diff --git a/README.md b/README.md index 1de756458a..a80c702233 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ sha256-verified. Fetch them with `cargo xtask corpus --all` and run the tests wi **New to PhotoCraft?** Download it from the [PhotoCraft page on getartcraft.com](https://getartcraft.com/apps/photocraft). That's the easiest way to install it. -**Want a specific build or format?** On GitHub, the [latest release](https://github.com/storytold/photocraft/releases/latest) has every build listed below, and [all releases](https://github.com/storytold/photocraft/releases) has earlier versions and their notes. `` in the file names is the version number, and `SHA256SUMS.txt` lists a checksum for every file. +**Want a specific build or format?** On GitHub, the [latest release](https://github.com/storytold/photocraft/releases/latest) has every build listed below, and [all releases](https://github.com/storytold/photocraft/releases) have earlier versions and their notes. `` in the file names is the version number, and `SHA256SUMS.txt` lists a checksum for every file. ### Windows diff --git a/contributors/people.toml b/contributors/people.toml index c5b0f2c0a7..7420dafd08 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -20,6 +20,10 @@ [people.brianblakely] sync_github_name = true +[people.abdul-karim-mia] +real_name = "Abdul karim mia" +sync_github_name = false + [people.jonathanwxh-cell] display_name = "jonathnwxh" sync_github_name = false @@ -230,3 +234,17 @@ sync_github_name = true [people.aqibmohd271] sync_github_name = true + +[people.marcoarnulfo] +real_name = "Marco Arnulfo" +display_name = "Marco Arnulfo" +sync_github_name = false + +[people.Gotix-eg] +real_name = "Ahmed Tammam" +display_name = "Gotix" +sync_github_name = true + +[people.adi-1108] +real_name = "Aditya Vishwakarma" +sync_github_name = true diff --git a/docs/architecture.md b/docs/architecture.md index 79c674bf23..f4b80270b7 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -555,7 +555,7 @@ codegen-units = 1 opt-level = 2 # image code is unusable at opt-level 0 ``` -Versions get pinned when we scaffold, using the numbers in `rust-framework-options.md`. +Dependency requirements are centralized in the workspace `Cargo.toml` (`[workspace.dependencies]`); `Cargo.lock` records the exact resolved versions. --- @@ -578,17 +578,16 @@ Each phase ends with a demoable build and green CI on all targets. Phases 8–10 ## 16. Open decisions -1. **UI toolkit.** The leading option is egui on winit + wgpu. Bevy, Slint and Qt are the alternatives (see `rust-framework-options.md`). This plan works with any of them. -2. **License.** MIT/Apache-2.0 is maximally reusable. GPL-3.0 is the Krita/GIMP model and protects against proprietary forks. Note that some candidate deps are LGPL (rawler) or GPL-or-commercial (Slint), and MIT/Apache would constrain which of them we can use. -3. **Project name.** `photocraft` (the repo name) is assumed. -4. **PSD crate.** Build our own, or adopt or fork `ag-psd`/`psd`. Decide after the week-1 spike. -5. **Default working depth and blending.** 8-bit + gamma-space blending matches Photoshop. 16-bit/float + linear is the modern default. The proposal is to follow Photoshop by default and make the other a per-document option. -6. **Scope of Photoshop plugin compatibility.** Decided: no native `.8BF` hosting; a sandboxed WebAssembly plug-in API instead (see [`plugins.md`](plugins.md)). +1. **License.** MIT/Apache-2.0 is maximally reusable. GPL-3.0 is the Krita/GIMP model and protects against proprietary forks. Note that some candidate deps are LGPL (rawler) or GPL-or-commercial (Slint), and MIT/Apache would constrain which of them we can use. +2. **Project name.** `photocraft` (the repo name) is assumed. +3. **PSD crate.** Build our own, or adopt or fork `ag-psd`/`psd`. Decide after the week-1 spike. +4. **Default working depth and blending.** 8-bit + gamma-space blending matches Photoshop. 16-bit/float + linear is the modern default. The proposal is to follow Photoshop by default and make the other a per-document option. ## Revision history | Date | Change | Summary | |---|---|---| +| 2026-10-21 | minor | Removed references to the deleted `rust-framework-options.md`; dropped the UI toolkit and plug-in items from Open decisions, both recorded as decided | | 2026-10-20 | minor | GPU display matrix/shaper path preserves near-black ICC values; complex transforms retain the 3D LUT fallback (#7958) | | 2026-10-13 | minor | Workspace size and `#[test]` counts recounted (393k → 539k lines, 5,460 → 8,040 tests); other current-state figures unchanged | | 2026-10-10 | minor | Current-state summary and deviations from the plan; status line; broken link to `photoshop-parity.md` repointed to `target-app-parity.md` | diff --git a/docs/development.md b/docs/development.md index 1b0a0cbfa6..034127ad53 100644 --- a/docs/development.md +++ b/docs/development.md @@ -400,6 +400,40 @@ different machine classes are never compared, and `--quick` numbers are only com quick baseline. Machines that run other work give noisy numbers: check the load average in the summary before trusting a regression, and record it next to any number you quote. +**Measuring one scenario.** A full run is tens of minutes; one row is seconds once its bench is +built. `xtask perf`'s own flags are `--quick`, `--update-baseline`, `--threshold`, `--bench` +(repeatable, one bench per name), `--skip-build` and `--reuse`; it has no way to pick a single +*row*. The bench binary takes those filters itself — `--only ` (row names are matched +with `contains`), `--group ` and `--reps N` — so to time one row run the bench directly: + +```sh +cargo run --release -p photocraft-ui-egui --example perf_scenarios -- \ + --only "Select > Modify > Smooth" --json target/perf/p26.json +``` + +That writes the row's own report and stops there: it does **not** evaluate the budget, so read +`p50_ms`/`p95_ms`/`max_ms` out of the JSON yourself. Use `cargo xtask perf --bench ` when you +want the verdict, the merged `results.json`/`summary.md` and the regression check. + +Read the sample count before quoting a percentile. `--reps` defaults to 7 in `--quick` and 15 +otherwise, it is per bench, and some rows cap it themselves (the `#211` algorithm rows use +`b.reps.min(5)`); percentiles are nearest-rank, so **at n ≤ 5 the "p95" is simply the maximum**. A +budgeted scenario the baseline has no row for fails the run (`missing` is in `Status::fails()`), +so add the `[[scenario]]` and its baseline row together. + +**A red `cargo xtask perf` is not always your budget.** The committed baseline carries +`failed.P12` (a wgpu panic writing `pc_compose_uniforms`), so the command exits non-zero before +any of this is about your change: read the row, not the exit code. And do not read the committed +verdicts as a clean-machine measurement: `perf/baseline.json` was written on 2026-10-05 at a load +average near 225 on a 14-core Apple M4 Pro, which is why P26 and P27 are over budget there while +`perf/budgets.toml` records 20–45 ms p95 for P26 on a clean 16-core machine (`enforce = false` +until CI cores are measured). Quote a number with the load average and the machine class beside it. + +**Flipping `enforce`.** `enforce = true` makes the nightly fail whenever that budget breaks, and a +baseline file belongs to exactly one machine class. Flip the row and refresh the baseline on the +nightly's own class in the same change, or `docs/scorecard.md` turns "BUDGET BROKEN" from numbers +nobody measured on that machine. + **Adding a scenario.** Add a row to a bench (keep its name stable: it is the key), give the bench `--json` support through `photocraft_testkit::perf::{row, report, write_report}`, then add a `[[scenario]]` to `perf/budgets.toml` and run `cargo xtask scorecard`. diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 1919a8af3c..b7ace1f654 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity: tools, handles, modifiers and feel -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (Hand tool ⌘ / ⌥ are Zoom In / Zoom Out while held, #6434; the wheel steps value fields, #5473; a dragged guide shows its X or Y beside the pointer, #7680; Type options bar "T" icon scrubs the font size, #6437; Move tool: dragged selected pixels take their own outline as the selection, #7538; artboard backgrounds remain below child Free Transform previews; ⌫/Delete removes the selected smart filter, #4751; Object Finder foreground/background discrimination (#6548); rounded triangle, polygon and star corners; shape Fill/Stroke popovers accept hex RGB values, #6176; a guide let go over either ruler is removed, #6286; Animated Zoom: holding the Zoom tool zooms continuously, #6210; shape Density and Feather, TOOL-214-12, #5846; Pen Auto Add/Delete on the path being drawn, #6140; Show Transform Controls frames every selected layer; guide keys and clicks; menu bar from the keyboard, #4988; Crop tool Content-Aware Fill; on-canvas corner radius widgets, TOOL-214-12; Curvature Pen edits the active path; Clip Thumbnails to Layer Bounds; Pen ⇧ 45°; last selected tool restored at launch; Layers panel filter-type dropdown with Kind, Name and Mode, #5048; Free Transform box and marching ants follow the selection, #5297; History: the trash deletes the selected snapshot, rows drag onto the trash, #5324; smart filter reordering by drag; smart object perspective row corrected to done, #426; Free Transform H/V skew fields; toolbar tool shortcuts can be reassigned; ⇧-dragged guides land on ruler ticks; layer and selection keys; tool keys; spring-loaded tool letters; type formatting keys; Pen Auto Add/Delete, #4144; smart filter rows; stale missing-tool rows corrected: Color Replacement, Red Eye and the Add/Delete Anchor Point pair are live toolbar tools, Frame landed; no percentage changed; selection-modifier and ⌥-wheel rows match their tests) · **Target:** Adobe Photoshop 2026 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (Hand tool ⌘ / ⌥ are Zoom In / Zoom Out while held, #6434; the wheel steps value fields, #5473; a dragged guide shows its X or Y beside the pointer, #7680; Type options bar "T" icon scrubs the font size, #6437; Move tool: dragged selected pixels take their own outline as the selection, #7538; artboard backgrounds remain below child Free Transform previews; ⌫/Delete removes the selected smart filter, #4751; Object Finder foreground/background discrimination (#6548); rounded triangle, polygon and star corners; shape Fill/Stroke popovers accept hex RGB values, #6176; a guide let go over either ruler is removed, #6286; Crop rows corrected: saved crop presets and Delete Cropped Pixels off are done, #6562; Animated Zoom: holding the Zoom tool zooms continuously, #6210; shape Density and Feather, TOOL-214-12, #5846; Pen Auto Add/Delete on the path being drawn, #6140; Show Transform Controls frames every selected layer; guide keys and clicks; menu bar from the keyboard, #4988; Crop tool Content-Aware Fill; on-canvas corner radius widgets, TOOL-214-12; Curvature Pen edits the active path; Clip Thumbnails to Layer Bounds; Pen ⇧ 45°; last selected tool restored at launch; Layers panel filter-type dropdown with Kind, Name and Mode, #5048; Free Transform box and marching ants follow the selection, #5297; History: the trash deletes the selected snapshot, rows drag onto the trash, #5324; smart filter reordering by drag; smart object perspective row corrected to done, #426; Free Transform H/V skew fields; toolbar tool shortcuts can be reassigned; ⇧-dragged guides land on ruler ticks; layer and selection keys; tool keys; spring-loaded tool letters; type formatting keys; Pen Auto Add/Delete, #4144; smart filter rows; stale missing-tool rows corrected: Color Replacement, Red Eye and the Add/Delete Anchor Point pair are live toolbar tools, Frame landed; no percentage changed; selection-modifier and ⌥-wheel rows match their tests) · **Target:** Adobe Photoshop 2026 How PhotoCraft's direct manipulation compares with Photoshop 2026 (27.11.0): the toolbox, the handles and modifier keys of each gesture, nudging, snapping, numeric precision, navigation, @@ -147,9 +147,9 @@ Triangle and Curvature Pen (minus legacy Rounded Rectangle and Targeted Adjustme | Crop shield, Show Cropped Area (H) | done | TOOL-214-22 | | Default mode: the image turns and moves behind an upright box; Auto Center Preview; Use Classic Mode | done | TOOL-214-23 | | W × H × Resolution with units, built-in size presets, Front Image | done | TOOL-214-9 (`crop_size.rs`) | -| Saved custom crop presets | missing | TOOL-214-9 | +| Saved custom crop presets (New / Delete Crop Preset, kept with the preferences; moving them to another computer is not offered) | done | TOOL-214-9 (`crop_presets.rs`, `tool.presets.*` with tool `crop`, #2723) | | Fill: Content-Aware Fill: a turned or extending crop fills the uncovered corners and strips from the image (each piece with its own sampling window), on the Background or else the active pixel layer, in the crop's one undo step; Generative Expand not offered | done | `tests/crop_content_aware.rs`, `crop_ui.rs::commit_passes_the_fill_choice` (#3823, 2026-10-12) | -| Delete Cropped Pixels off | partial | | +| Delete Cropped Pixels off: the crop only hides the pixels outside the frame; editing the frame again shows them past the canvas and a recrop handle snaps to their edge | done | `image.crop` `deleteCroppedPixels`, the options-bar checkbox (`panels.rs`), `tests/crop_beyond_canvas.rs::editing_a_crop_frame_shows_the_pixels_past_the_canvas`, `crop_mode.rs::a_recrop_handle_snaps_exactly_to_the_hidden_pixels_edge` (#3638, #3640) | ## Snapping, guides, rulers, precision @@ -282,6 +282,7 @@ Paragraph; the rest open as in-app floating windows. | 2026-10-14 | minor | Last selected tool restored across launches; temporary overrides excluded, explicit Eraser choices retained, and final selection flushed on normal exit | | 2026-10-13 | minor | Free Transform with a selection: the box is the selection's rectangle, and the marching ants follow it while it is transformed (#5297) | | 2026-10-14 | minor | Move › Show Transform Controls frames every selected layer and outlines the selected shapes | +| 2026-10-21 | minor | Crop rows corrected against the code and the scorecard: saved custom crop presets (TOOL-214-9, #2723) and Delete Cropped Pixels off are done (#6562); no percentage changed | | 2026-10-20 | minor | Free Transform of an artboard child omits ancestor paper from the covering and isolated composites while retaining board clipping. The base canvas and whole-artboard backgrounds are preserved; synthetic regressions in `transform_tool.rs`. No parity percentage changed. | | 2026-10-18 | minor | Move tool: dragging selected pixels shrinks the marching ants to the moved pixels as the drag starts, and the dropped selection is theirs, as in Photoshop (#7538) | | 2026-10-15 | minor | ⌫/Delete with a smart filter row selected removes that filter (the Smart Filters row: all of them), as the Delete button does, instead of the smart object (#4751) | diff --git a/docs/user/troubleshooting/canvas-size-white-border.md b/docs/user/troubleshooting/canvas-size-white-border.md new file mode 100644 index 0000000000..f8a41eb5a3 --- /dev/null +++ b/docs/user/troubleshooting/canvas-size-white-border.md @@ -0,0 +1,32 @@ +--- +title: Canvas Size added a white border around my photo +description: Why Image > Canvas Size fills the new canvas with white around a photo, and how to extend the canvas with transparency instead. +order: 25 +verified: 0.11.1 +--- + +The photo did not change size. **Image** ▸ **Canvas Size…** only moves the layers; the new canvas area is filled with the **Canvas extension color**, which defaults to **Background** — your background color, usually white. Choose **Transparent** there to add empty canvas instead. + +## Symptoms + +- You open a photo, choose **Image** ▸ **Canvas Size…**, make the canvas larger and click **OK**. +- The photo's layer now has a white or background-colored border, so the layer looks resized or padded. +- The photo content itself keeps its size and position. + +## Fix + +1. Choose **Image** ▸ **Canvas Size…** again. +2. Set **Canvas extension color:** to **Transparent**. +3. Enter the canvas size you want and click **OK**. + +The new area is now empty. If you also want to change the photo's size, use **Image** ▸ **Image Size…**; that scales the image pixels. + +## Why this happens + +An opened JPEG or another fully opaque image becomes a locked **Background** layer, as in Photoshop. A Background layer cannot contain transparent pixels, so when the canvas grows, PhotoCraft fills the new area with the canvas extension color. **Background** is the default, so the border takes your background color — white until you change it. Layers that are not the Background are only moved and keep transparency. + +Canvas Size never scales layer pixels: it moves them by the **Anchor:** you choose. Scaling is **Image** ▸ **Image Size…**'s job. + +## Still stuck? + +If the photo itself is scaled or cropped, or the border appears with **Transparent** selected, report it in the [issue tracker](https://github.com/storytold/photocraft/issues) with your version from **Help** ▸ **System Info…** and a screenshot of the Canvas Size settings. diff --git a/docs/user/troubleshooting/index.md b/docs/user/troubleshooting/index.md index 4785ae412b..a92501d040 100644 --- a/docs/user/troubleshooting/index.md +++ b/docs/user/troubleshooting/index.md @@ -8,4 +8,5 @@ verified: 0.8.2 - [PhotoCraft does not start](photocraft-does-not-start.md) - [The Windows taskbar turns black when PhotoCraft is maximized](taskbar-turns-black.md) - [Text moves when I start editing a PSD text layer](text-moves-when-editing.md) +- [Canvas Size added a white border around my photo](canvas-size-white-border.md) - [Get help with a problem](get-help.md) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 544e427a2e..7136f90ecf 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -22,7 +22,7 @@ const USAGE: &str = "\ usage: cargo xtask commands: - layers enforce the crate dependency layering (plan/architecture.md §3) + layers enforce the crate dependency layering (docs/architecture.md §3) wasm cargo check --target wasm32-unknown-unknown for the wasm-safe crates ci fmt --check, clippy -D warnings, test, layers, wasm (stops at first failure) corpus [--all | --pngsuite | --psd | --psd-tools | --pixls | --atn | --photoshop | --gimp] [--local] [--update-manifest] @@ -106,7 +106,7 @@ pub fn metadata() -> Result { fn cmd_layers() -> Result<(), String> { let crates = layers::from_metadata(&metadata()?)?; - println!("Dependency layering (plan/architecture.md §3)\n"); + println!("Dependency layering (docs/architecture.md §3)\n"); println!("{:<28} {:<14} workspace deps", "crate", "layer"); for c in &crates { let ws: Vec = c