mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-02 18:13:39 +00:00
Record native preview benchmarks and review evidence
This commit is contained in:
+2
-2
@@ -90,8 +90,8 @@ The other built-in ICC profiles and the generated LUT looks are produced by code
|
|||||||
| `docs/images/preferences-apply-*.png` | Preferences before and after adding Apply (no artwork) | PhotoCraft contributors | PhotoCraft control-channel capture and offscreen `snapshot` example | MIT OR Apache-2.0 |
|
| `docs/images/preferences-apply-*.png` | Preferences before and after adding Apply (no artwork) | PhotoCraft contributors | PhotoCraft control-channel capture and offscreen `snapshot` example | MIT OR Apache-2.0 |
|
||||||
| `docs/images/gradient-overlay-method-*.png` | Gradient Overlay Method selector, before and after (synthetic gradient) | @oRE-o | Rendered offscreen with the `snapshot` example; no external artwork | MIT OR Apache-2.0 |
|
| `docs/images/gradient-overlay-method-*.png` | Gradient Overlay Method selector, before and after (synthetic gradient) | @oRE-o | Rendered offscreen with the `snapshot` example; no external artwork | MIT OR Apache-2.0 |
|
||||||
| `docs/images/cpu-pixel-alignment/*.png` | CPU canvas pixel alignment, before and after #8321 | PhotoCraft contributors | Original synthetic noise rendered with the offscreen `snapshot` example; see the adjacent README | MIT OR Apache-2.0, adjacent `LICENSE-MIT` and `LICENSE-APACHE` |
|
| `docs/images/cpu-pixel-alignment/*.png` | CPU canvas pixel alignment, before and after #8321 | PhotoCraft contributors | Original synthetic noise rendered with the offscreen `snapshot` example; see the adjacent README | MIT OR Apache-2.0, adjacent `LICENSE-MIT` and `LICENSE-APACHE` |
|
||||||
| `docs/images/multi-channel-editing/{curves,curves-choices,levels,drawing}.png` | Selected colour channel editing (synthetic gradient) | @hidoba | Original synthetic gradient rendered with the offscreen `snapshot` example; adjacent JSON scripts | MIT OR Apache-2.0, adjacent `LICENSE-MIT` and `LICENSE-APACHE` |
|
| `docs/images/multi-channel-editing/{curves,curves-choices,levels,drawing,mask-view-before,mask-view-after}.png` | Selected colour channel editing (synthetic gradient) | @hidoba | Original synthetic gradient rendered with the offscreen `snapshot` example; adjacent JSON scripts | MIT OR Apache-2.0, adjacent `LICENSE-MIT` and `LICENSE-APACHE` |
|
||||||
| `docs/images/multi-channel-editing/great-wave.jpg`, `rgb.png`, `cmyk.png`, `lab.png` | *The Great Wave off Kanagawa*, c. 1830–1832, and PhotoCraft channel-selection captures | Katsushika Hokusai (artwork); PhotoCraft contributors (UI) | [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Tsunami_by_hokusai_19th_century.jpg), Metropolitan Museum reproduction; adjacent JSON capture scripts | Public domain artwork (PD-Art), adjacent `LICENSE-PUBLIC-DOMAIN.txt`; UI/scripts MIT OR Apache-2.0 |
|
| `docs/images/multi-channel-editing/great-wave.jpg`, `rgb.png`, `cmyk.png`, `lab.png`, `gallery-channel-preview-{before,after}.png` | *The Great Wave off Kanagawa*, c. 1830–1832, and PhotoCraft channel-selection captures | Katsushika Hokusai (artwork); PhotoCraft contributors (UI) | [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Tsunami_by_hokusai_19th_century.jpg), Metropolitan Museum reproduction; adjacent JSON capture scripts | Public domain artwork (PD-Art), adjacent `LICENSE-PUBLIC-DOMAIN.txt`; UI/scripts MIT OR Apache-2.0 |
|
||||||
|
|
||||||
Artwork shown in the screenshots (all public domain, via Wikimedia Commons; details in
|
Artwork shown in the screenshots (all public domain, via Wikimedia Commons; details in
|
||||||
[`docs/images/SOURCES.md`](docs/images/SOURCES.md)):
|
[`docs/images/SOURCES.md`](docs/images/SOURCES.md)):
|
||||||
|
|||||||
@@ -19,5 +19,27 @@
|
|||||||
"candidate_source_commit": "aeff53449221e8b42a628d832abc595c9503a146",
|
"candidate_source_commit": "aeff53449221e8b42a628d832abc595c9503a146",
|
||||||
"candidate_tone_source_sha256": "da42e686db09ed4e5bf2c19316a6b47261c97185aec83c6bba86831e29e39c6e",
|
"candidate_tone_source_sha256": "da42e686db09ed4e5bf2c19316a6b47261c97185aec83c6bba86831e29e39c6e",
|
||||||
"before_fused_rerun_resources": "elapsed_seconds=16.47 peak_rss_kib=690516",
|
"before_fused_rerun_resources": "elapsed_seconds=16.47 peak_rss_kib=690516",
|
||||||
"before_fused_commit": "15bf0873ba03622e54189e668df20a1403e41d56"
|
"before_fused_commit": "15bf0873ba03622e54189e668df20a1403e41d56",
|
||||||
|
"liquify_preview": {
|
||||||
|
"source_content_commit": "009c4fa6128dad1e564d731656a304953d879842",
|
||||||
|
"build_git_head": "a37d21de7e1a90451d5afcad6772ae358f69e07a",
|
||||||
|
"binary_sha256": "309bc5e882403080ade9410103fba6eb73b296da897274a7816380f50e413c3a",
|
||||||
|
"files_sha256": {
|
||||||
|
"crates/ui-egui/examples/multi_channel_preview_bench.rs": "84f4e6fd4a0f79a35da706a06144401ad06509a06608ea5ed89729d394f6e0b1",
|
||||||
|
"crates/ui-egui/src/liquify_ui.rs": "1179cb7403fa189a8e0d1872212e150bd98b10844d189140e954cd0cdd279f29",
|
||||||
|
"crates/ui-egui/src/gallery_ui.rs": "118249270993f1443882df4808fd0f6e05b129a091441f3560bdcc0e4d80a4ca",
|
||||||
|
"crates/ui-egui/src/wide_angle_ui.rs": "6ebf9f1d291642dc3cc42c72980830f79e9f82c6d9b743c145e9048adddb1b36",
|
||||||
|
"crates/engine/src/color_channels.rs": "e6737a5dfb79a5324f08173d18cf271a539dddb386b9488249c150810537313e"
|
||||||
|
},
|
||||||
|
"measured_at_utc": "2026-10-25T17:45:11.612486+00:00",
|
||||||
|
"profile": "release, thin LTO",
|
||||||
|
"rayon_threads": 8,
|
||||||
|
"measurement": "CPU field deformation plus dirty native proxy render. GPU texture upload/presentation excluded. One warmup plus six interleaved updates; p50 is upper middle; max_ms is max of six. open_ms is one sequential setup timing per mode, not a median.",
|
||||||
|
"resources": {
|
||||||
|
"elapsed_seconds": 0.72,
|
||||||
|
"peak_rss_kib": 389664
|
||||||
|
},
|
||||||
|
"isolation": "Measured after final tests, builds and cargo xtask perf --quick ended; no Cargo or rustc processes were running.",
|
||||||
|
"correctness_proof": "Each workspace reports a nonzero Liquify maxDisplacement after the measured updates."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"depth": 16,
|
||||||
|
"operation": "Liquify 120px live move; native proxy preview",
|
||||||
|
"repeats": 6,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"max_ms": 0.120119,
|
||||||
|
"open_ms": 22.648719,
|
||||||
|
"p50_ms": 0.110929,
|
||||||
|
"samples_ms": [
|
||||||
|
0.111069,
|
||||||
|
0.097649,
|
||||||
|
0.10152,
|
||||||
|
0.120119,
|
||||||
|
0.10755999999999999,
|
||||||
|
0.110929
|
||||||
|
],
|
||||||
|
"target": "all"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"max_ms": 0.36013799999999996,
|
||||||
|
"open_ms": 246.61311,
|
||||||
|
"p50_ms": 0.34547799999999995,
|
||||||
|
"samples_ms": [
|
||||||
|
0.310718,
|
||||||
|
0.30684799999999995,
|
||||||
|
0.320127,
|
||||||
|
0.352098,
|
||||||
|
0.34547799999999995,
|
||||||
|
0.36013799999999996
|
||||||
|
],
|
||||||
|
"target": "red"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"max_ms": 0.461107,
|
||||||
|
"open_ms": 317.70011900000003,
|
||||||
|
"p50_ms": 0.447017,
|
||||||
|
"samples_ms": [
|
||||||
|
0.397908,
|
||||||
|
0.394428,
|
||||||
|
0.416098,
|
||||||
|
0.448617,
|
||||||
|
0.447017,
|
||||||
|
0.461107
|
||||||
|
],
|
||||||
|
"target": "red_blue"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
6000,
|
||||||
|
4000
|
||||||
|
],
|
||||||
|
"warmups": 1
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 952 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 474 KiB |
@@ -0,0 +1,51 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "image.imageSize",
|
||||||
|
"params": {
|
||||||
|
"width": 1000,
|
||||||
|
"resample": "bicubic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "channel.target",
|
||||||
|
"params": {
|
||||||
|
"colors": [
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "channel.setVisible",
|
||||||
|
"params": {
|
||||||
|
"channel": "composite",
|
||||||
|
"visible": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ui.menu.invoke",
|
||||||
|
{
|
||||||
|
"id": "filter.filterGallery"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ui.menu.invoke",
|
||||||
|
{
|
||||||
|
"id": "filter.filterGallery",
|
||||||
|
"params": {
|
||||||
|
"ui": {
|
||||||
|
"filter": "cutout"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 133 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
@@ -0,0 +1,128 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "file.new",
|
||||||
|
"params": {
|
||||||
|
"width": 800,
|
||||||
|
"height": 560,
|
||||||
|
"depth": 16,
|
||||||
|
"background": "white"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "paint.gradient",
|
||||||
|
"params": {
|
||||||
|
"from": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
800,
|
||||||
|
560
|
||||||
|
],
|
||||||
|
"colors": [
|
||||||
|
"#143b9b",
|
||||||
|
"#f6b552"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "layer.layerMask.revealAll",
|
||||||
|
"params": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "channel.target",
|
||||||
|
"params": {
|
||||||
|
"colors": [
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "view.layerMask",
|
||||||
|
"params": {
|
||||||
|
"mode": "gray"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ui.set",
|
||||||
|
{
|
||||||
|
"tool": "brush",
|
||||||
|
"activeTabs": {
|
||||||
|
"middle": "channels"
|
||||||
|
},
|
||||||
|
"fit": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "tools.setColors",
|
||||||
|
"params": {
|
||||||
|
"foreground": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"engine.execute",
|
||||||
|
{
|
||||||
|
"command": "tools.setBrush",
|
||||||
|
"params": {
|
||||||
|
"brush": {
|
||||||
|
"size": 70,
|
||||||
|
"hardness": 1.0,
|
||||||
|
"smoothing": {
|
||||||
|
"amount": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ui.pointer",
|
||||||
|
{
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"kind": "down",
|
||||||
|
"x": 150,
|
||||||
|
"y": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "move",
|
||||||
|
"x": 400,
|
||||||
|
"y": 280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "move",
|
||||||
|
"x": 650,
|
||||||
|
"y": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "up",
|
||||||
|
"x": 650,
|
||||||
|
"y": 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
@@ -146,15 +146,30 @@ Raw samples: [original pre-feature base](benchmarks/multi-channel-editing/baseli
|
|||||||
[environment and binary hashes](benchmarks/multi-channel-editing/environment.json).
|
[environment and binary hashes](benchmarks/multi-channel-editing/environment.json).
|
||||||
The three `candidate*-before-fused-original.json` files preserve the original feature run.
|
The three `candidate*-before-fused-original.json` files preserve the original feature run.
|
||||||
|
|
||||||
Validation: 6,472 engine and UI tests passed (38 ignored), including 53 focused engine
|
Validation after the main merge and review fixes: 6,542 engine and UI tests passed
|
||||||
regressions. Strict all-target Clippy, formatting, layering,
|
(38 ignored), including 58 focused engine regressions. Strict all-target Clippy,
|
||||||
wasm, parity generation and the adversarial command panic hunt passed. The complete
|
formatting, layering, wasm, parity generation and the adversarial command panic hunt passed.
|
||||||
`cargo xtask perf --quick` runner was attempted: its layout fixture fails because
|
The complete `cargo xtask perf --quick` runner now finishes successfully after the main
|
||||||
it tries to convert an already imported Smart Object. This unrelated runner failure is not
|
merge; its report still lists unrelated, non-enforced budget overruns. Six further local
|
||||||
reported as a passing full performance gate. The feature-specific benchmark above
|
review/fix rounds followed the latest CodeRabbit review; the last found no actionable issues.
|
||||||
completed successfully. Eleven original review/fix rounds and four follow-up reviews were completed; the
|
All visual captures were rendered offscreen and inspected.
|
||||||
latest review found no actionable issues. Visual captures were rendered offscreen
|
|
||||||
and inspected.
|
The additional 24 MP RGB16 Liquify benchmark uses
|
||||||
|
`crates/ui-egui/examples/multi_channel_preview_bench.rs`, with eight Rayon workers and
|
||||||
|
no concurrent builds, tests or performance scenarios. It measures field deformation plus
|
||||||
|
the dirty CPU proxy render; GPU uploads/presentation are excluded. One warmup and six
|
||||||
|
measured updates use interleaved target modes. The field's nonzero displacement is checked.
|
||||||
|
|
||||||
|
| Liquify, 120 px brush | All channels | Red only | Red + Blue |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| Preview update p50 | 0.111 ms | 0.345 ms | 0.447 ms |
|
||||||
|
| Maximum of six updates | 0.120 ms | 0.360 ms | 0.461 ms |
|
||||||
|
| Open workspace, one setup measurement | 22.6 ms | 246.6 ms | 317.7 ms |
|
||||||
|
|
||||||
|
Opening a component workspace builds its native proxy once. Open timings are sequential
|
||||||
|
single observations, not medians; setup is excluded from update timings. The whole process,
|
||||||
|
including all three workspaces, peaked at 380.5 MiB RSS. [Raw samples](benchmarks/multi-channel-editing/liquify-preview.json)
|
||||||
|
and [source/binary hashes](benchmarks/multi-channel-editing/environment.json) are retained.
|
||||||
|
|
||||||
|
|
||||||
## Visual evidence
|
## Visual evidence
|
||||||
@@ -200,3 +215,59 @@ retouch-preview restriction issues are fixed, with regression tests. The propose
|
|||||||
clipboard inversion is unnecessary: destination extraction and scatter already convert
|
clipboard inversion is unnecessary: destination extraction and scatter already convert
|
||||||
between tone and ink. Tests cover implicit/explicit singleton copying into explicit targets
|
between tone and ink. Tests cover implicit/explicit singleton copying into explicit targets
|
||||||
and normal/Paste Into/Paste Outside paths at 8/16/32-bit depths without adding an inversion.
|
and normal/Paste Into/Paste Outside paths at 8/16/32-bit depths without adding an inversion.
|
||||||
|
|
||||||
|
The post-merge review identified missing Copy/Cut parameter documentation; both commands
|
||||||
|
now describe explicit colour targets and layer ids/names. Auditing that contract also found
|
||||||
|
that Copy ignored an explicit layer with an implicit single-component target. Copy now
|
||||||
|
resolves that layer just as Cut does, with a regression that failed before the correction.
|
||||||
|
|
||||||
|
The proposed Lab histogram index change is unnecessary: `HistSource::Colors` reserves
|
||||||
|
slot 0 for the combined selection and stores native components at `k + 1`, including Lab.
|
||||||
|
Ordinary Lab histograms instead use slots 0–2 for L/a/b. The histogram accessor returns
|
||||||
|
empty bins for an unavailable slot, rather than panicking. New tests use actual native
|
||||||
|
subset histograms for components `[0, 2]`, render every choice in both editors, and check
|
||||||
|
Levels Auto against those bins in RGB/CMYK/Lab at 8/16/32-bit depths.
|
||||||
|
|
||||||
|
The next local review found that viewing a layer mask after selecting colour channels
|
||||||
|
could still send canvas strokes to those colours. Mask view now takes priority in canvas
|
||||||
|
painting, implicit engine commands and captured tone-dialog targets. Regressions cover
|
||||||
|
live previews and commits plus adjustments/filters at all depths, while explicit native
|
||||||
|
targets can still override the viewed mask. Selecting colour rows again leaves mask view
|
||||||
|
and restores their editing target and visibility.
|
||||||
|
|
||||||
|
A further review caught mask Cut/Clear falling back to layer pixels, dialog capture retaining
|
||||||
|
the old colour set over a viewed mask, and Desaturate dropping explicit target parameters.
|
||||||
|
Cut/Clear now copy/fill only the selected mask samples, respecting locks and feathering.
|
||||||
|
Adjustment/filter dialogs preserve their captured mask through preview, confirmation and
|
||||||
|
Curves Reset, and Desaturate forwards its target and layer parameters. Tests cover pixel and
|
||||||
|
clipboard preservation, undo/redo, refused edits and changed panel selections.
|
||||||
|
Move/Free Transform still work on the viewed unlinked mask when colour rows remain selected
|
||||||
|
behind it; native colour subsets retain their geometry restriction. Curves canvas-point
|
||||||
|
gestures also honour the captured scalar target after the panel selection changes.
|
||||||
|
|
||||||
|
The named-destination visibility guard now resolves layer names just as painting does.
|
||||||
|
Filter Gallery, Liquify and Adaptive Wide Angle capture the native colour target, preserve
|
||||||
|
unselected components and alpha in their specialized previews, and retain the target for OK.
|
||||||
|
Liquify samples only the dirty proxy or detail viewport, with a native proxy built once.
|
||||||
|
These full-window workspaces require layer pixels or native colour channels; masks, saved
|
||||||
|
channels and Quick Mask remain available through ordinary filter commands/dialogs.
|
||||||
|
Geometric writeback retains the original alpha layout, copying selected native bytes even
|
||||||
|
when the warp produces an alpha-bearing surface. Changed defaults materialize only within
|
||||||
|
the supplied document/preview extent, under the same bounded-pixel limit.
|
||||||
|
|
||||||
|
The public-domain Filter Gallery comparison uses R+B with Green manually made visible.
|
||||||
|
The original preview filters Green too; the corrected preview retains its original detail.
|
||||||
|
The [capture script](images/multi-channel-editing/gallery-channel-preview-script.json)
|
||||||
|
records that explicit visibility override.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The same synthetic gradient and [pointer script](images/multi-channel-editing/mask-view-script.json)
|
||||||
|
show the correction: before, the mask stays white while the stroke changes the layer;
|
||||||
|
after, the stroke paints the mask while the layer's gradient stays intact.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|||||||
Reference in New Issue
Block a user