Framing shall include perspective correction — vertical and horizontal keystone — applied before the crop.
Why
The one corrective operation with no implementation anywhere in the tree. framing.rs offers ±45° straighten, quarter turns and flips; there is no perspective transform.
Converging verticals are the commonest geometric fault in architectural and interior work, and correcting them reframes the image, which is why the order relative to the crop is not a preference. framing.rs already documents and enforces the coordinate order this has to slot into.
Acceptance
Runs in the coordinate chain between framing and the lens warp, preserving the documented space: output pixel → framing → warp (lens) → sample → colour ops → output.
Carries Attribute::Compose, so it is withheld from a settings paste by default alongside the crop — for the same reason.
Interacts correctly with max_inscribed_crop, which must account for the transform.
Priority
Low, and genre-bound. Listed so its absence is a decision rather than an oversight.
Part of the Develop Ergonomics spec (FR-DEV series proposal), 2026-09-05.
Framing shall include **perspective correction** — vertical and horizontal keystone — applied before the crop.
## Why
The one corrective operation with no implementation anywhere in the tree. `framing.rs` offers ±45° straighten, quarter turns and flips; there is no perspective transform.
Converging verticals are the commonest geometric fault in architectural and interior work, and correcting them **reframes the image**, which is why the order relative to the crop is not a preference. `framing.rs` already documents and enforces the coordinate order this has to slot into.
## Acceptance
- [ ] Runs in the coordinate chain between framing and the lens warp, preserving the documented space: `output pixel → framing → warp (lens) → sample → colour ops → output`.
- [ ] Carries `Attribute::Compose`, so it is withheld from a settings paste by default alongside the crop — for the same reason.
- [ ] Interacts correctly with `max_inscribed_crop`, which must account for the transform.
## Priority
Low, and genre-bound. Listed so its absence is a decision rather than an oversight.
---
Part of the Develop Ergonomics spec (FR-DEV series proposal), 2026-09-05.
Renumbered from FR-DEV-19, which the spec assigned to mask editing on 2026-09-06. The clause enters the spec as FR-DEV-20 when it is built; per the 2026-09-19 audit the spec's numbering wins over a number reserved in a title.
Renumbered from FR-DEV-19, which the spec assigned to mask editing on 2026-09-06. The clause enters the spec as FR-DEV-20 when it is built; per the 2026-09-19 audit the spec's numbering wins over a number reserved in a title.
The keystone is two framing parameters, keystone_v and keystone_h (-100..100), so it inherits framing's Compose attribute, sidecar storage, paste scoping, history and reset. Sidecars written before this read as no correction.
The shader's coordinate chain is output → crop → straighten → perspective → orientation → lens warp → sample, so "vertical" means vertical as the photo is shown. The CPU copies of the map apply the same step, so masks, gradients and spot handles follow it. The output frame maps onto a trapezoid inside the source, so a keystone alone never leaves black corners.
max_inscribed_crop searches numerically when a keystone is set, and every rect it returns is tested corner by corner. Without a keystone the closed form is unchanged.
UI: Vertical and Horizontal sliders under Straighten in Compose. Releasing one refits the crop, each move is an undoable step, and the Compose reset clears them.
Sign: positive Vertical spreads the top, which fixes a building shot from below. Positive Horizontal spreads the right side. This deliberately does not follow Lightroom's sign.
Tests: the pipeline maths (no sampling outside the source, converging lines come out parallel, sideways-stored files, the crop search), a GPU render check, and a develop session test. Also checked in the app.
Not covered: the segmentation region overlay does not follow the keystone (it already ignored quarter turns; noted in masks.rs). There is no Aspect or Scale slider. Saving from the running app was not exercised; the sidecar round trip is covered by a unit test.
Merged to master: ff89a4f, 5a50011, 114d979. FR-DEV-20 is now in requirements.md.
- The keystone is two framing parameters, `keystone_v` and `keystone_h` (-100..100), so it inherits framing's `Compose` attribute, sidecar storage, paste scoping, history and reset. Sidecars written before this read as no correction.
- The shader's coordinate chain is output → crop → straighten → perspective → orientation → lens warp → sample, so "vertical" means vertical as the photo is shown. The CPU copies of the map apply the same step, so masks, gradients and spot handles follow it. The output frame maps onto a trapezoid inside the source, so a keystone alone never leaves black corners.
- `max_inscribed_crop` searches numerically when a keystone is set, and every rect it returns is tested corner by corner. Without a keystone the closed form is unchanged.
- UI: Vertical and Horizontal sliders under Straighten in Compose. Releasing one refits the crop, each move is an undoable step, and the Compose reset clears them.
- Sign: positive Vertical spreads the top, which fixes a building shot from below. Positive Horizontal spreads the right side. This deliberately does not follow Lightroom's sign.
- Tests: the pipeline maths (no sampling outside the source, converging lines come out parallel, sideways-stored files, the crop search), a GPU render check, and a develop session test. Also checked in the app.
Not covered: the segmentation region overlay does not follow the keystone (it already ignored quarter turns; noted in `masks.rs`). There is no Aspect or Scale slider. Saving from the running app was not exercised; the sidecar round trip is covered by a unit test.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Framing shall include perspective correction — vertical and horizontal keystone — applied before the crop.
Why
The one corrective operation with no implementation anywhere in the tree.
framing.rsoffers ±45° straighten, quarter turns and flips; there is no perspective transform.Converging verticals are the commonest geometric fault in architectural and interior work, and correcting them reframes the image, which is why the order relative to the crop is not a preference.
framing.rsalready documents and enforces the coordinate order this has to slot into.Acceptance
output pixel → framing → warp (lens) → sample → colour ops → output.Attribute::Compose, so it is withheld from a settings paste by default alongside the crop — for the same reason.max_inscribed_crop, which must account for the transform.Priority
Low, and genre-bound. Listed so its absence is a decision rather than an oversight.
Part of the Develop Ergonomics spec (FR-DEV series proposal), 2026-09-05.
FR-DEV-19 — Perspective correctionto FR-DEV-20 — Perspective correctionRenumbered from FR-DEV-19, which the spec assigned to mask editing on 2026-09-06. The clause enters the spec as FR-DEV-20 when it is built; per the 2026-09-19 audit the spec's numbering wins over a number reserved in a title.
Merged to master:
ff89a4f,5a50011,114d979. FR-DEV-20 is now in requirements.md.keystone_vandkeystone_h(-100..100), so it inherits framing'sComposeattribute, sidecar storage, paste scoping, history and reset. Sidecars written before this read as no correction.max_inscribed_cropsearches numerically when a keystone is set, and every rect it returns is tested corner by corner. Without a keystone the closed form is unchanged.Not covered: the segmentation region overlay does not follow the keystone (it already ignored quarter turns; noted in
masks.rs). There is no Aspect or Scale slider. Saving from the running app was not exercised; the sidecar round trip is covered by a unit test.