Commit Graph
2767 Commits
Author SHA1 Message Date
David MannandClaude Opus 5.5 45c5001dfd GPU canvas: convert changed layer tiles once each, in parallel (#1847)
Changed tiles are grouped by shared Arc and converted once each in parallel batches; a tile
shared by several coordinates (a solid fill, a cleared area, a non-zero default) is uploaded
once to a scratch tile and copied on the GPU, and new pages take the same path. 16-bit RGB and
gray tiles convert to RGBA16F through a lookup table. Select All + Fill on a 50 MP 16-bit page
refreshes in 15-19 ms instead of about 1.4 s. Fixes #1774.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:04:21 -04:00
AntonandClaude Opus 5.5 96cdff7454 Free Transform: edge handles scale proportionally, Shift stretches one axis (#1873)
As in Photoshop CC 2019 and later, dragging an edge handle now scales both axes by the same
factor about the middle of the box (Alt: about the reference point), and Shift stretches the
one axis. Use Legacy Free Transform restores the old behaviour for edges as well as corners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:04:14 -04:00
AlonandClaude Opus 5.5 9ba843290e Ctrl+T on a selected path transforms the path (#1871)
With Path Selection or Direct Selection and a path, Edit > Free Transform (and the Transform
items) frame the path's anchors and handles and commit one path.transform step instead of
moving pixels; it works on the Background too. Distort and Perspective, which an affine path
transform can't follow, report an error. Fixes #1770.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:04:08 -04:00
Jason XiangandClaude Opus 5.5 a4a2583280 Layer comps: an edited layout becomes the Last Document State (#1862)
Applying a comp after ordinary edits now backs up the edited layout instead of keeping
a stale one; cycling or reapplying unedited comps keeps the original backup. The check
runs only on comp application and respects each comp's capture flags. Fixes #919.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:04:07 -04:00
David MannandClaude Opus 5.5 7c2bd89e6a Canvas: edits that change no pixels (a lasso selection) don't recomposite (#1853)
Session::edit now reports empty damage when the step leaves every layer's pixels and the
document's compositing fields untouched (same_pixels: a bounded walk over the layer tree
comparing tiles by pointer), so a selection change no longer recomposites the whole canvas.
Adds a lasso_bench example. Fixes #1773.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:03:25 -04:00
rafi612andClaude Opus 5.5 f39abb70b9 System title bar: hide the in-app mark and title, show the file in the OS title (#1903)
With Preferences › Interface › System Title Bar (Windows/Linux) the OS draws its own icon and title,
so the in-app bar drops the brand mark and centred title; the OS window title follows the active
file ("name — PhotoCraft"), sent only when it changes. macOS and the web build are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:02:53 -04:00
kayrockscreenprintingandClaude Opus 5.5 83b0bcedbe Schema dialogs ignore notes after a spec; Selective Color's OK works again (#1886)
parse_spec reads only the spec's leading {…} object, so notes and → {…} results after
it no longer become junk fields. Selective Color's per-range arrays stop showing as a
number field, and its OK applies again (it failed since #1790 tightened the params).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:59:25 -04:00
David MannandClaude Opus 5.5 d7078f3623 Layers: a new empty layer no longer recomposites every layer (#1846)
step_damage pairs layers by id and steps over a layer only one side has when it draws nothing
(an empty pixel layer or group in Normal/Pass Through, no masks, effects or Advanced Blending,
not a clipping base), so New Layer, New Group and their undo/redo report no damage. Adds the
P51 perf scenario (new layer in the 150-layer document). Fixes #1771.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:59:12 -04:00
kayrockscreenprintingandClaude Opus 5.5 21649f6030 Filter dialogs: OK keeps the preview until the filter lands (#1825)
A filter that runs as a background job no longer flashes the unfiltered image after OK: the dialog's preview is marked committing and the canvas keeps showing it (canvas::committed_filter_preview) until the job lands, fails or is cancelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:59:10 -04:00
Jason XiangandClaude Opus 5.5 175603a644 New adjustment and fill layers get a white mask (#1858)
As in Photoshop, a new adjustment or fill layer with no selection or active path gets
an enabled, linked reveal-all mask (sparse, no canvas-sized allocation), ready to paint,
in the layer's single undo step. Fixes #1768.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:48:26 -04:00
kayrockscreenprintingandClaude Opus 5.5 71e89906ee Dialogs: tall filter dialogs scroll; a moved dialog reopens in place (#1822)
Fixes #1829. A schema-built filter body sits in a scroll area capped to the window, so Flame's OK and Cancel stay on screen in a small window; a dialog the user drags is remembered per command (or per kind) in egui's persisted memory and reopens there, as Photoshop does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:48:07 -04:00
Neil VerschooreandClaude Opus 5.5 e7a83ca342 Add Dutch (nl) UI translation (#1877)
A complete Dutch catalog (nl.tsv) registered with the complete-menu languages (nl-NL and nl-BE
resolve to it) and in the coverage test, with the strings added on main since the PR opened and
Dutch bit-depth labels (8-bits, 16-bits).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:41:23 -04:00
kayrockscreenprintingandClaude Opus 5.5 99e8ac2d2a Fill: pick the Custom Pattern from its swatches (#1861)
Edit > Fill… with Contents: Pattern shows a swatch of the current pattern; a click opens a grid of every library pattern (current one outlined, names in tooltips and accessibility labels), as in Photoshop, instead of a dropdown of names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:31:53 -04:00
Dan HiornsandClaude Opus 5.5 13d50312bd Brush preset grid: centre the columns and indent every row (#1883)
The preset grid (Brush Preset picker and Brushes tab) wrapped with horizontal_wrapped, which
indented only the first row and left the spare width on the right. Rows are now laid out as
whole columns with the same centred margin (grid_columns, guarded against NaN/zero sizes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:29:07 -04:00
David MannandClaude Opus 5.5 dee737313a Masks: \ toggles the layer mask's rubylith overlay (#1809)
New command layer.toggleMaskOverlay, bound to \ as in Photoshop: shows the active layer's
mask as a 50% red rubylith over the composite and hides it again (view state, not a history
step; the mask stays the paint target). Disabled without a layer mask; a focused text field
keeps the key. Fixes #1765.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:28:55 -04:00
Jason XiangandClaude Opus 5.5 c645f951b0 Panels: clicking a tab of a collapsed panel expands it (#1868)
A single click on a tab of a collapsed panel group (or a pick from its overflow menu) now
expands the group and shows that tab, as in Photoshop; double-click still collapses. The tab
strip reports clicks through StripOut/CardResponse. Tested in both themes, with the
workspace locked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:28:49 -04:00
Jason XiangandClaude Opus 5.5 941b84a748 Save As and export dialogs start beside the document (#1866)
Export dialogs suggested a bare file name, so the save dialog opened wherever the OS chose.
pick_save now joins a bare suggestion onto the active document's folder (an explicit directory
or an untitled document is left as is), and the desktop dialog starts in the suggestion's folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:28:42 -04:00
kayrockscreenprintingandClaude Opus 5.5 d4884d33fa Rasterize Vector Mask keeps a feathered edge (#1807)
Layer > Rasterize > Vector Mask multiplied the hard vector mask into the pixel mask, dropping
the vector mask's feather (and the pixel mask's feather or density). When any of those shape
the edge, the pixel mask now becomes exactly the combined mask the compositor showed, with
feather and density baked in. Tested at 8, 16 and 32 bits against the composite. Fixes #992.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:28:36 -04:00
AnandandClaude Opus 5.5 fb23c18581 Placed layer no longer disappears when editing Smart Object contents (#1831)
Opening a just-placed Smart Object's contents switched documents while the Place transform was
pending, and end_if_left cancelled it, deleting the placed layer from the parent. When the
active document is that layer's linked child, the transform session now just ends and the
layer stays. Fixes #1797.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:28:14 -04:00
Stevan MarjanovicandClaude Opus 5.5 b81808f6bc Double-click the Hand tool to fit on screen (#1796)
Double-clicking the Hand tool's toolbar button selects it and runs View > Fit on Screen, as in
Photoshop; a single click only picks the tool, and with no document open the double-click is
harmless. Also credits the author in contributors/people.toml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:27:49 -04:00
David MannandClaude Opus 5.5 a42f7f0004 Crop tool: Enter crops to the selection's frame (#1806)
With a selection, the Crop tool's untouched default frame frames the selection's bounds, but
Enter on it did nothing (it was always treated as the no-op whole-canvas frame). It now only
skips the commit when the frame is the whole canvas. Tested at 8 and 16 bits. Fixes #1789.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:27:22 -04:00
Max MurphyandClaude Opus 5.5 dcf8ceef85 Rulers: the horizontal ruler keeps its ticks under View > Flip Horizontal (#1835)
With the view flipped, to_doc returns the ruler's ends reversed, so the tick loop ran zero
times. ruler_range normalises the visible range to (min, max) for both rulers. Fixes #1814.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:27:12 -04:00
MarekSpodBiedryandClaude Opus 5.5 d4634bb30f Layers panel: thumbnails of tall documents fill the thumbnail height (#1805)
The layer thumbnail drew the checkerboard over the whole square cell while the texture is the
document letterboxed into it, so tall (or wide) documents showed checker bars at the sides.
The checker and image are now drawn only over the document-shaped part (channels_panel::fit_thumb).
Fixes #1762.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:27:02 -04:00
Max MurphyandClaude Opus 5.5 1bccee1fda Desktop app: exit with status 2 when --control setup fails (#1834)
A bad control token, token plus token file, or a missing automation root printed an
error and exited 0, so a launcher believed the control server was up. Now exits 2,
like a bad --control port; subprocess tests cover the four paths. Fixes #1816.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:26:02 -04:00
kayrockscreenprintingandClaude Opus 5.5 749109e6fb Define Pattern asks for the pattern's name (#1850)
Edit > Define Pattern… now opens a Pattern Name dialog, prefilled with the name the pattern would get anyway (pattern_cmds::default_name, now shared with the command so the two can't drift), as Photoshop does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:22:50 -04:00
DmytroandClaude Opus 5.5 fee2240d59 Add Ukrainian UI translation (#1799)
A complete Ukrainian catalog (uk.tsv) registered with the complete-menu languages, with
one/few/many plurals (teens use many), uk-UA locale resolution, and the coverage test entry;
plus the strings added on main since the PR opened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:18:31 -04:00
kayrockscreenprintingandClaude Opus 5.5 86a629939a RAW: check the orientation range before narrowing (#1839)
The five RAW readers cast the u32 Orientation to u16 before the 1..=8 check, so a LONG
65538 became 2 and rotated the image. A shared tiff::orientation checks the range on
the u32 first. Fixes #1817.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:14:28 -04:00
kayrockscreenprintingandClaude Opus 5.5 5012e4c32f Image Size reports the size it applied (#1836)
image.imageSize returns the document's size after the command instead of the requested
one, so Resample off (which changes the resolution only) no longer reports a size it
ignored. Tested at 8, 16 and 32 bits. Fixes #1815.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:13:12 -04:00
Max MurphyandClaude Opus 5.5 0050f29a85 Native format: reject a leading '+' in hex pairs (#1832)
u8::from_str_radix accepts a leading '+', so "+5" decoded as 0x05; unhex now requires
two ASCII hex digits per pair and fails as corrupt otherwise. Fixes #1818.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:03:52 -04:00
Reynold HuandClaude Opus 5.5 d4a5d974ac File > Open starts in the last-used folder (#1889)
Open dialogs (File > Open and the single-file pickers for Place, scripts, notes and presets) now carry the most recent file's folder as initial_dir, and the desktop shell hands it to rfd's set_directory. Flips UI-217-3 to done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:03:31 -04:00
texjerandClaude Opus 5.5 e8b992a6f0 Preferences on Cmd+K, Search on Cmd+F (Photoshop's defaults) (#1900)
Edit › Preferences › General takes ⌘K and Edit › Search moves to ⌘F, as in Photoshop. Nothing
else was bound to ⌘F (Filter › Last Filter stays ⌘⌥F); the shortcut-capture tests follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:02:30 -04:00
xsvmandClaude Opus 5.5 9b69ba89aa image.mode.bitmap: an unknown halftone shape is a bad-params error (#1842)
HalftoneShape::from_id returns None for an unlisted id instead of silently falling
back to Round, and the command fails naming the valid shapes (part of #507).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:02:04 -04:00
Reynold HuandClaude Opus 5.5 a14e94617b Hue/Saturation: hue-following colour tracks on the Saturation and Lightness sliders (#1863)
The Saturation slider's track runs grey to the hue at full saturation and the Lightness track black, the hue, white; both recolour as the Hue slider moves, as in Photoshop. Flips UI-217-16 to done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:56:04 -04:00
Reynold HuandClaude Opus 5.5 d7d5c6069c Test the dispatch panic guard end to end; mark REL-3 done (#1803)
A cfg(test)-only test.panic command drives Session::execute into the catch_unwind
guard in jobs::dispatch; the test asserts an internal-error Err, an untouched document
and history, and a working session afterwards. REL-3 flips to done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:51:51 -04:00
Sir StarchandClaude Opus 5.5 d8d2df4fba camera_raw_scope: serialise the GPU tests with GPU_LOCK (#1856)
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>
2026-10-09 11:51:40 -04:00
Sir StarchandClaude Opus 5.5 9bd935498b GPU parity tests: give the one-step tolerance f32 slack (#1852)
Two results exactly one 8-bit step apart can land a few f32 ulps past 1/255 after premultiplying
and subtracting (NVIDIA, Hue/Saturation in adjustment_layers); allow 4 epsilon of slack. Fixes #1848.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:51:36 -04:00
Reynold HuandClaude Opus 5.5 ff1d933cb8 Scorecard: mark the BUG-205 and BUG-207 rows done; the fixes and their tests are on main (#1795)
BUG-205-1/2/3 (5a5facc, #242) and BUG-207-1/2/3 (e2444ce, #234) flip to done;
each row now names the test that covers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:40:48 -04:00
Reynold HuandClaude Opus 5.5 7544bb1084 Scorecard: mark UI-217-2 done; the geometry round trip is tested (#1808)
UI-217-2 flips to done: b07a2e5b (#587) persists the window and dock width via eframe,
and window_and_panel_geometry_survive_a_restart asserts the round trip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:39:33 -04:00
BFlat 9823f4ea08 Merge pull request #1898 from storytold/community/1734-fxdrag
Layers panel: drag layer effects onto another layer, Alt-drag copies (#1734 carry)
2026-10-09 22:09:00 +07:00
BFlat 46c9b83ce1 Merge pull request #1897 from storytold/community/1706-corpus
xtask: corpus --all --update-manifest refetches every manifest (#1706 carry)
2026-10-09 22:08:56 +07:00
BFlat bf1795e490 Merge pull request #1896 from storytold/community/1462-labmix
Compose: restore the blend-space flags instead of clearing them after a tile (#1462 carry)
2026-10-09 22:08:53 +07:00
BFlat e45c79df3d Merge pull request #1895 from storytold/community/1636-pixls
Raw corpus: real camera files from raw.pixls.us, oracle-tested (#1636 carry)
2026-10-09 22:08:50 +07:00
BFlat 9a71884244 Merge pull request #1894 from storytold/community/1745-eyedropper
Eyedropper: Sample Size, Sample layers and Show Sampling Ring (#1745 carry)
2026-10-09 22:08:46 +07:00
a01a3eb2de Canvas zoom: Photoshop's range, ladder and ⌥-wheel step; no slide at the limit (#1718)
* Canvas zoom: Photoshop's range, ladder and ⌥-wheel step; no slide at the limit

Measured on Photoshop 25.4 (exact zoom read by script, view position by screenshot
registration):
- ⌥ + wheel: ×1.1 a notch around the pointer (was ×1.05);
- the zoom runs up to 12800 % and down to where the document's shorter side is 2 px; a wheel
  notch past a limit lands on it exactly and further notches change nothing;
- Zoom In / Zoom Out and a Zoom tool click step along 0.1 … 66.67, 100, 200 … 800, 1200, 1600,
  3200, 6400, 12800 %, from any zoom to the next rung;
- with Overscroll off, an image smaller than the view stays where zooming put it, inside the
  view, instead of being re-centred.

PhotoCraft capped the zoom at 3200 % without meaning to: the status bar's zoom field (and the
Navigator's slider) clamped it and wrote it back every frame. Past 3200 % each ⌥-wheel notch
zoomed around the pointer and the field pulled the zoom back, so the image slid towards the
pointer. All zoom paths now share `zoom_levels` (range, ladder, clamp), and the fields always
include the current zoom.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Zoom: Print Size, 200 % and Fit Layers on Screen clamp through zoom_levels too

Review of #1718: `view.twoHundredPercent` / `view.printSize` and `view.fitLayersOnScreen` still
clamped to 1-6400 % on their own. They now use `zoom_levels::clamp` like every other zoom path:
Print Size at 1 ppi gives 7200 % (it was cut to 6400 %), and fitting a 1 px layer stops at
12800 %.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Wheel zoom: a notch applies on the frame it arrives (from #1490)

Folded in from @Jeremy8776's #1490, as agreed in review: zooming reads this frame's wheel
events (`Input::raw`) instead of egui's smoothed delta, so each notch is one ×1.1 step on the
frame it arrives, with no easing over the next frames (his frame-by-frame recordings of
Photoshop). The smoothed tail neither zooms nor turns into a pan when Alt is released; panning
still uses the smoothed delta. Zoom with Scroll Wheel uses the same ×1.1 notch (assumed, not
measured).

The Filter Gallery's and Liquify's own preview zooms keep their 3200 % caps, now with a comment
saying why: they aren't the canvas.

Co-authored-by: Jeremy <jeremy8776@googlemail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeremy <jeremy8776@googlemail.com>
2026-10-09 22:08:18 +07:00
buivanvu94 69f307afbb fix(ui): autoscroll Layers panel when dragging past top or bottom edges (#1728) 2026-10-09 22:08:13 +07:00
MarekSpodBiedry 5538f3920d Patch Tool: say to drag the selection after the lasso and after a click that moves nothing (#1715) (#1720)
The Patch and Content-Aware Move tools work in two steps: lasso the
area, then drag the selection. The first step only leaves a selection,
and a press and release inside the selection returns early because the
offset is zero, so neither step said what to do next.

After a lasso made with either tool, and after a drag inside the
selection that moves nothing, the status bar now says "Now drag the
selection onto another area". The string is translated in all 14
catalogs.
2026-10-09 22:08:10 +07:00
2b139ebd5c inspect: report each smart object's source (#1785)
document.inspect (and photocraft-cli info) now gives every smart object a
smartSource: embedded with its file name, or linked with a file path or,
for a PSD placed layer, the Idnt uuid its duplicates share. A script can
then tell duplicates of one placed file from independent contents, e.g.
to check that every location in a mockup template has its own art.

Co-authored-by: Juan Manuel Verges Solanas <jmverges@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:08:07 +07:00
Gert Pellin - GPE 310e46e6a0 fix(ui): one Cmd+V pastes one layer (#1638) (#1743)
On macOS AppKit runs Edit > Paste's key equivalent and takes the
key-down, but winit still delivers the key-up (it forwards key-ups
while Cmd is held). The native menu fed egui a press plus a made-up
release, and clipboard_keys treats a Cmd+V release without a paste
event as an image paste (egui-winit drops Cmd+V when the clipboard
holds no text). So one Cmd+V pasted three times: the menu's press,
its synthetic release, and winit's real release.

- native_menu: a key equivalent adds only its press, inserted ahead of
  its key-up when that is already queued; winit delivers the release.
- clipboard_keys: a Cmd+V key press that reached egui marks the paste
  as seen, so its release doesn't paste again; any V/Insert release
  clears the mark (releasing Ctrl before V left it stale and swallowed
  the next image paste on Linux/Windows).
2026-10-09 22:08:03 +07:00
Gert Pellin - GPE 58289b2822 fix(codecs): read the resolution from EXIF, XMP and Photoshop APP13; write it consistently (#1691) (#1744)
A camera JPEG (e.g. Nikon D3200) records its resolution only in EXIF IFD0, so it
opened at 72 ppi instead of 300, and saving wrote JFIF 72 next to the source's
EXIF 300.

Reading: a JPEG takes the first usable of APP13 Photoshop ResolutionInfo
(0x03ED), XMP tiff:X/YResolution, EXIF IFD0 X/YResolution + ResolutionUnit
(2 inch, also when absent; 3 cm), and only then the JFIF density. That is the
order Photoshop resolves them in; JFIF comes last because cameras write EXIF
only while many tools write a default JFIF 72. Other formats (WebP, HEIF, PNG
without pHYs, ...) fall back to XMP then EXIF when they have no resolution of
their own. All parsing is bounds-checked; malformed data reads as no
resolution.

Writing: JPEG, PNG, WebP, TIFF (XMP), PSD and Save for Web JPEG rewrite the
EXIF/XMP resolution to the image's: RATIONAL values in place, ResolutionUnit 2,
and an entry that can't be rewritten safely is removed from IFD0 without moving
any other data.
2026-10-09 22:07:59 +07:00
BFlatandClaude e39cacd895 Packaging: Windows MSI setup wizard (folder choice, progress, version, launch on finish) (#1864)
Double-clicking the MSI now opens WixUI_InstallDir without the licence page:
Welcome, install folder (remembered for upgrades in
HKLM\Software\PhotoCraft\InstallDir), Ready, progress, and a Finish page with
a ticked "Launch PhotoCraft" box (WixShellExec, unelevated). The pages name
the full version, pre-release tag included (photocraft.en-us.wxl).
package.ps1 draws the banner and side bitmaps from the app icon, so no WiX
stock art ships. msiexec /qn still installs silently. The release and ARM64
workflows install the WiX UI and Util extensions; packaging-lint checks the
.wxl. Ported from VectorCraft (storytold/vectorcraft@aa548d2).


Claude-Session: https://claude.ai/code/session_01AP7ThRN3kpNmjsuJUerJui

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-09 21:33:38 +07:00