Skip to content

feat(layers): rasterize layers outside a live editor session - #853

Merged
hm21 merged 2 commits into
stablefrom
feat/layer-rasterizer
Jul 31, 2026
Merged

feat(layers): rasterize layers outside a live editor session#853
hm21 merged 2 commits into
stablefrom
feat/layer-rasterizer

Conversation

@hm21

@hm21 hm21 commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

Adds LayerRasterizer and LayerRasterizerHost so layers can be captured without a running editor.

Layer.captureAsPng returns null for an unmounted layer, so layers restored from an exported state history could not be baked into a render — they deserialize fine, but they have never been laid out. The host mounts them behind its own child (painted, never visible) so capture() returns the same ExportedLayers a live session would.

final rasterizer = LayerRasterizer();

MaterialApp(
  builder: (context, child) => LayerRasterizerHost(
    rasterizer: rasterizer,
    child: child!,
  ),
);

final history = ImportStateHistory.fromMap(persistedHistory);
final captured = await rasterizer.capture(
  layers: history.stateHistory[history.editorPosition].layers,
  editorBodySize: history.lastRenderedImgSize,
);

Concurrent capture calls are serialized so they cannot read each other's repaint boundaries, and layers whose content loads asynchronously (network images, decoded assets, custom WidgetLayers) are held until awaitContentReady resolves.

Example

New page under Layers → Rasterize Layers without Editor: parses a persisted state history, rasterizes its layers with no editor involved, and shows the result both composited over the background image and layer-by-layer on a transparency grid.

Testing

  • flutter test test/shared/services/layer_rasterizer/ — 6 tests, all passing
  • Ran the example on macOS: 5 of 5 layers captured in ~310 ms (emoji, text, paint, and two widget layers, one of them backed by a network image that resolves through awaitContentReady)
  • flutter analyze clean for package and example

hm21 added 2 commits July 31, 2026 12:53
Add `LayerRasterizer` and `LayerRasterizerHost`. `Layer.captureAsPng`
returns `null` for an unmounted layer, so layers restored from an
exported state history could not be baked into a render. The host mounts
them behind its own child - painted, never visible - so `capture()`
returns the same `ExportedLayer`s a live session would.

Layers whose content loads asynchronously (network images, decoded
assets, custom `WidgetLayer`s) are handled via `awaitContentReady`.

Includes an example page that renders a persisted history without ever
opening the editor.
Queued captures shared the host's `LayerWidget` element: the host never
rebuilds with an empty request between two captures, and `LayerWidget`
resolves its layer type only in `initState`. A follow-up capture of a
different layer type therefore built the previous type and was dropped
(`EmojiLayer is not a subtype of TextLayer`). Key the widgets by layer
identity.

Disposing the rasterizer mid-capture replaced a finished result with
"used after being disposed", because `notifyListeners` throws once
`dispose` ran.

The host mounted layers without the `Theme` and `Material` the editor
supplies, so layer content inherited MaterialApp's error text style and
Material-based widget layers asserted. Both paths now share the editor's
fallback theme.

Also assert the one-host and not-mounted-elsewhere rules that duplicate
layer GlobalKeys, throw instead of returning an empty list when the host
stops rendering mid-capture, default `basePixelRatio` to
`configs.imageGeneration.customPixelRatio`, keep captured layers out of
the semantics tree, and document that `configs` must match the session
that created the layers.
@hm21
hm21 merged commit 0a2dbda into stable Jul 31, 2026
1 check passed
@hm21
hm21 deleted the feat/layer-rasterizer branch July 31, 2026 12:16
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