Repository navigation
fix(main-editor): keep layers on the image point when cropping and re… - #874
Merged
Merged
Conversation
…opening crops Layer offsets on a cropped image were derived from crop editor units and rescaled on import by the uncropped image fit. Layers jumped when a crop was applied while the image filled the editor by its width, and drifted when a cropped history was reopened in a body of another shape. ImageLayerFrame maps a layer offset to a point on the image and back, using the transforms the background is painted with. Applying a crop places each layer on the image point it covered before, and importing a cropped history maps layers from the exported body to the current one. The export records the editor body size; older exports estimate it from cropEditorScreenRatio. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mage points The crop editor undid the active crop for its layer preview with LayerTransformGenerator, which no longer matches how the main editor places layers on a cropped image. Map the preview layers with ImageLayerFrame onto the uncropped image box, and again once that box is laid out, since _setRawLayers first runs before layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mage points - Histories exported before `editorBodySize` only estimate the body when `lastRenderedImgSize` is fitted to it. A blank canvas or a raw video size is not, and moved its layers even on the same device. The current body is used when the image fits it the same way, so reopening on the exporting device no longer drifts for crops that fit the body by height. - Version 1.0.0 offsets keep the plain rescale. - The crop editor places the layers it closes with on the same image points as the main editor, so the hero flight no longer starts with a jump. - Applying a crop also rescales the slide distance of layer animations. - The frame pair and layer move live in `ImageLayerFrame`, and an import builds the frames once per history step.
# Conflicts: # CHANGELOG.md # pubspec.yaml
hm21
approved these changes
Sep 30, 2026
hm21
left a comment
Owner
There was a problem hiding this comment.
Thanks a lot for this PR, @saif-ellafi. Mapping the layers through the same transforms the background is painted with is the right fix, and measuring painted positions in raw image pixels made it easy to verify across surfaces.
Here is what changed on top of your commits:
- Old exports on a blank canvas stay put. Histories exported before
editorBodySizeestimated the body fromlastRenderedImgSize, but a blank canvas (and a raw-size video) records its unscaled size there, so layers moved by ~60 px even on the same device. Those exports keep the previous rescale now. - Old exports reopened on the same device no longer drift. When the image fits the current body the same way, that body is used instead of the
cropEditorScreenRatioestimate, which describes the crop editor's area rather than the main body (a 1:2 crop on a phone moved layers by ~5 px). - The crop editor closes without a jump.
done()still placed the fake-hero layers withLayerTransformGeneratorand the old transform, so the hero flight started ~37 px off on portrait screens. - Applying a crop also rescales
slideFromof layer animations, as the import already did. Version 1.0.0 imports keep the plain rescale, since their offsets aren't measured from the body center. - The frame pair and the layer move live in
ImageLayerFramenow, shared by the main editor, the crop editor and the import (which builds the frames once per history step). - Added tests for flipped crops, cropping a cropped image, blank canvases, a same-device reopen of an old export and the closing crop editor.
- Merged
stable, bumped the release to 14.6.1 and shortened the CHANGELOG entries.
Thanks again!
Contributor
Author
|
Thanks to you for building on top and improving further! What the editor can do is unbelievable so far! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hello,
This is my second attempt to fix crop on images with history in them, editable layers. AI-Assisted, I am still testing this but so far so good.
Description
Layers on a cropped image did not stay on the image point they were placed on:
LayerTransformGeneratorderives layer offsets from crop editor units (offset,scaleUser,scaleRotation,fitToScreenFactor), which don't match how the main editor paints the cropped background.uncropped image fit (
decodedImageSize / lastRenderedImgSize), which doesn't describe where the crop window is painted.Fix
A new
ImageLayerFramemaps a layer offset (distance from the body center) to a point on the image and back. It followsthe transforms
TransformedContentGeneratorpaints the background with: theFittedBoxcontain scale,fitFactor,scaleUser, crop pan, rotation and flips. Its fit helper is now a public static,TransformedContentGenerator.fitFactor, so painting and mapping share one implementation.LayerTransformGeneratorstill provides layer rotation and flips; only offset and scale are replaced.editorBodySize(minified keye). Histories exported before this estimate the body fromlastRenderedImgSizeandcropEditorScreenRatio(the crop editor's area aspect on the same screen).previous behaviour, since their perspective isn't linear.
Tests
test/features/main_editor/crop_reopen_across_devices_test.dartmeasures where each layer is painted, in raw imagepixels, through the background's render box (
globalToLocal, so rotation is accounted for). It covers:editorBodySizeOn
stable, 11 of the original 15 scenarios fail. With this change all 23 pass, and the full suite passes.Test note:
CropRotateEditorState.done()never completes when awaited directly in the fake-async zone while layersexist, so the test pumps until it finishes and has a 45 s timeout.
Supersedes #871. It keeps #871's
editorBodySizeexport field and key, but also fixes the shift when a crop is applied,and handles histories exported before the field existed.
Related Issue: Closes #
Type of Change