feat: combine paint layers + flatten filters#847
Merged
Conversation
Add a real "merge/combine paint layers" feature so a host app can reduce several selected draw layers to a single PaintLayer, distinct from grouping. - PaintLayer now holds List<PaintedModel> items; `item` is a back-compat getter/setter mapping to items.first. Single-stroke layers serialize the legacy `item`; merged layers additionally serialize an `items` array (minified key `it`), and fromMap still reads legacy single-item payloads. - PaintLayerMergeManager.merge bakes each source layer's offset/scale/rotation/ flip/opacity (and stroke width, erase radius) into a shared frame so the merged layer has an identity transform and looks pixel-identical. - Public API on ProImageEditorState: `PaintLayer? mergeSelectedLayers()` and `bool get canMergeSelectedLayers`; wrapper `handleMergeLayers()` in the layers service. Merge is inserted at the top-most source z-index, removes the originals, selects the new layer, and records a single undo entry. - Renderer iterates all items (per-stroke opacity for merged layers); hit checks and copy managers updated for multiple items. - Example: "Combine Paint Layers" button in the layer grouping example. Bumps version to 13.2.0.
Filters are pure color matrices that the editor already renders as a single combined ColorFilter.matrix, so flattening the accumulated filter stack into one FilterState is appearance-identical. Add ProImageEditorState.mergeFilters() / canMergeFilters (gated to >=2 filters with no video-timeline metadata), backed by the pure, tested mergeFilterStates()/canMergeFilterStates() helpers. Not applied to text layers (a single TextLayer cannot represent multiple independently transformed/styled texts) or tune adjustments (the tune editor restores each slider by id, which flattening would break).
…nhanced eraser functionality
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.
Summary
Adds appearance-preserving combine/flatten operations, so a host app can reduce clutter without changing how the result looks.
Tracking issue in the consuming app:
divine-mobile#5834.1. Combine paint layers (the original ask)
Reduce several selected draw (paint) layers to a single
PaintLayer. A true merge to one layer — distinct from grouping (groupId).PaintLayernow holdsList<PaintedModel> items;itemstays a back-compat getter/setter toitems.first. Single-stroke layers serialize legacyitem; merged layers add anitemsarray (minifiedit);fromMapstill reads legacy payloads.PaintLayerMergeManager.merge(...)bakes each source layer's offset/scale/rotation/flip/opacity (+ stroke width, erase radius) into a shared frame → identity-transform layer that renders pixel-identical.2. Flatten filters
ColorFilterGeneratoralready composes the whole filter+tune stack into oneColorFilter.matrix(mergeColorMatrices), so flattening N filter states reuses that math and is appearance-identical. The filter editor re-reads prior filters as an opaqueallMatricesbase, so re-editing still works. Gated out when any filter carries video-timeline scheduling.mergeFilterStates()/canMergeFilterStates().Why not text layers / tune adjustments
TextLayeris one string + one style + one transform; N independently positioned/rotated/styled texts can't be baked into one without losing appearance or editability. Grouping already covers "handle together".el.id == item.id), so flattening to one opaque matrix would reset every slider to 0 on re-open. The re-editability regression isn't worth it. (Happy to add it opt-in if you want the trade-off.)Tests
flutter test→ 507 pass,dart analyze(lib, test, example) clean. New coverage:itemgetter returnsitems.first.Housekeeping