Skip to content

feat(crop-rotate): perspective tilt/skew with auto-zoom bounds (#525)#842

Merged
hm21 merged 3 commits into
stablefrom
feat/skew
Jun 29, 2026
Merged

feat(crop-rotate): perspective tilt/skew with auto-zoom bounds (#525)#842
hm21 merged 3 commits into
stablefrom
feat/skew

Conversation

@hm21

@hm21 hm21 commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Implements #525 — adds a tilt (perspective/skew) transform to the crop-rotate editor, ported onto the current stable layout (using origin/feat/skewing as reference; its directory reorg was intentionally not adopted).

What's included

  • Tilt UI — rotate / horizontal / vertical rulers via a TiltProvider, exposed through the new CropRotateTool.tilt bottom-bar entry and TiltConfigs/TiltStyle/TiltWidgets. Hidden in the video editor by default (non-quarter rotations aren't natively exportable).
  • Auto-zoom bounds — the crop selection can never end up outside the (tilted) image. _setOffsetLimits keeps the crop rect inside the convex image quad via half-plane erosion and binary-searches the minimal zoom. Manual vs. tilt-induced zoom are tracked separately, so straightening the image zooms back out to (but never below) the user's manual zoom; the tilt is clamped when maxScale can't keep the crop covered.
  • Consistent composition — a single transform order (scale → translate → tilt, applied as separate center-aligned transforms) is shared by the live preview, TransformedContentGenerator (export + main editor), the bounds math and the darken mask, so the preview matches the exported result under perspective.
  • Perspective-aware darken overlay in CropCornerPainter (follows the tilted image quad, preserving the [Bug]: In the cCropRotateEditor, when the image is at the bottom, the top edge of the image is not covered by crop OverlayOpacity #776 overscan and feat(crop-rotate): keep aspect ratio orientation on rotate #829 frame-opacity behavior).
  • Serialization — tilt angles round-trip through TransformConfigs (export/import).

Also

  • fix(example): guard onCloseEditor pops with Navigator.canPop — pressing Esc in the root-route editor previously crashed with Bad state: No element.

Tests

  • New unit/widget tests: tilt provider, rulers, ruler chooser, the auto-zoom bounds math (fitCropInsideTiltedImage) and the tilt-aware darken mask.
  • flutter analyze clean; full suite green (480 tests).

Notes

  • Tilt is applied as a display/render transform (baked into the screenshot export), not into the native crop parameters — consistent with the reference implementation.

hm21 added 3 commits June 29, 2026 17:05
)

Adds a tilt (perspective/skew) tool to the crop-rotate editor, ported onto
the current stable layout.

- Tilt UI (rotate/horizontal/vertical rulers) via a TiltProvider, gated by
  the new CropRotateTool.tilt entry and TiltConfigs (hidden in the video
  editor since non-quarter rotations aren't natively exportable).
- Auto-zoom bounds: the crop selection can never leave the tilted image.
  _setOffsetLimits keeps the crop rect inside the convex image quad via
  half-plane erosion and binary-searches the minimal zoom; manual vs
  tilt-induced zoom are tracked separately so straightening zooms back out,
  and the tilt is clamped when maxScale can't keep the crop covered.
- A single transform composition (scale -> translate -> tilt as separate
  centered transforms) is shared by the live preview,
  TransformedContentGenerator, the bounds math and the darken mask, so the
  preview matches the exported result under perspective.
- Perspective-aware darken overlay in CropCornerPainter.
- Tilt angles are serialized in TransformConfigs for export/import.

Adds unit/widget tests for the tilt provider, rulers, the auto-zoom bounds
math and the tilt-aware darken mask.
Pressing Esc in the main editor when it is the root route called
Navigator.pop with an empty history, throwing "Bad state: No element".
Guard both onCloseEditor pops with Navigator.canPop so closing is a safe
no-op when there is nothing to pop.
The tilt is baked into the exported image, but consumers using
onCompleteWithParameters (re-applying transforms themselves instead of
using the rendered bytes) had no way to know about it. Add
tiltRotate/tiltHorizontal/tiltVertical to CompleteParameters (toMap,
fromMap, copyWith, toString) and populate them from the active
TransformConfigs in both the main editor and the crop editor.
@hm21 hm21 merged commit e67e025 into stable Jun 29, 2026
1 check passed
@hm21 hm21 deleted the feat/skew branch June 29, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant