Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 63b6117

Browse files
[skwasm] Clip pictures if they go beyond the bounds of the window. (#50887)
This fixes flutter/flutter#143800, where we are attempting to capture an image that is way too large. We only need to render the part of the image that will be visible in the window.
1 parent cd57dc9 commit 63b6117

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-5
lines changed

lib/web_ui/lib/src/engine/scene_view.dart

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ typedef RenderResult = ({
2020
// composite pictures into the canvases in the DOM tree it builds.
2121
abstract class PictureRenderer {
2222
FutureOr<RenderResult> renderPictures(List<ScenePicture> picture);
23+
ScenePicture clipPicture(ScenePicture picture, ui.Rect clip);
2324
}
2425

2526
class _SceneRender {
@@ -86,20 +87,49 @@ class EngineSceneView {
8687
}
8788
}
8889

90+
ScenePicture _clipPictureIfNeeded(ScenePicture picture, ui.Rect clip) {
91+
final ui.Rect pictureRect = picture.cullRect;
92+
if (pictureRect.left >= clip.left &&
93+
pictureRect.top >= clip.top &&
94+
pictureRect.right <= clip.right &&
95+
pictureRect.bottom <= clip.bottom) {
96+
// The picture is already within the clip bounds.
97+
return picture;
98+
}
99+
100+
return pictureRenderer.clipPicture(picture, clip);
101+
}
102+
103+
ui.Rect? _getScreenBounds() {
104+
final DomScreen? screen = domWindow.screen;
105+
if (screen == null) {
106+
return null;
107+
}
108+
return ui.Rect.fromLTWH(0, 0, screen.width, screen.height);
109+
}
110+
89111
Future<void> _renderScene(EngineScene scene, FrameTimingRecorder? recorder) async {
112+
final ui.Rect? screenBounds = _getScreenBounds();
113+
if (screenBounds == null) {
114+
// The browser isn't displaying the document. Skip rendering.
115+
return;
116+
}
90117
final List<LayerSlice> slices = scene.rootLayer.slices;
91118
final List<ScenePicture> picturesToRender = <ScenePicture>[];
119+
final List<ScenePicture> originalPicturesToRender = <ScenePicture>[];
92120
for (final LayerSlice slice in slices) {
93-
if (slice is PictureSlice) {
94-
picturesToRender.add(slice.picture);
121+
if (slice is PictureSlice && !slice.picture.cullRect.isEmpty) {
122+
originalPicturesToRender.add(slice.picture);
123+
final ScenePicture clippedPicture = _clipPictureIfNeeded(slice.picture, screenBounds);
124+
picturesToRender.add(clippedPicture);
95125
}
96126
}
97127
final Map<ScenePicture, DomImageBitmap> renderMap;
98128
if (picturesToRender.isNotEmpty) {
99129
final RenderResult renderResult = await pictureRenderer.renderPictures(picturesToRender);
100130
renderMap = <ScenePicture, DomImageBitmap>{
101131
for (int i = 0; i < picturesToRender.length; i++)
102-
picturesToRender[i]: renderResult.imageBitmaps[i],
132+
originalPicturesToRender[i]: renderResult.imageBitmaps[i],
103133
};
104134
recorder?.recordRasterStart(renderResult.rasterStartMicros);
105135
recorder?.recordRasterFinish(renderResult.rasterEndMicros);
@@ -125,10 +155,11 @@ class EngineSceneView {
125155
}
126156
}
127157

158+
final ui.Rect clippedBounds = slice.picture.cullRect.intersect(screenBounds);
128159
if (container != null) {
129-
container.bounds = slice.picture.cullRect;
160+
container.bounds = clippedBounds;
130161
} else {
131-
container = PictureSliceContainer(slice.picture.cullRect);
162+
container = PictureSliceContainer(clippedBounds);
132163
}
133164
container.updateContents();
134165
container.renderBitmap(renderMap[slice.picture]!);

lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,4 +482,14 @@ class SkwasmPictureRenderer implements PictureRenderer {
482482
@override
483483
FutureOr<RenderResult> renderPictures(List<ScenePicture> pictures) =>
484484
surface.renderPictures(pictures.cast<SkwasmPicture>());
485+
486+
@override
487+
ScenePicture clipPicture(ScenePicture picture, ui.Rect clip) {
488+
final ui.PictureRecorder recorder = ui.PictureRecorder();
489+
final ui.Canvas canvas = ui.Canvas(recorder, clip);
490+
canvas.clipRect(clip);
491+
canvas.drawPicture(picture);
492+
493+
return recorder.endRecording() as ScenePicture;
494+
}
485495
}

lib/web_ui/test/engine/scene_view_test.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,14 @@ class StubPictureRenderer implements PictureRenderer {
4343
);
4444
}
4545

46+
@override
47+
ScenePicture clipPicture(ScenePicture picture, ui.Rect clip) {
48+
clipRequests[picture] = clip;
49+
return picture;
50+
}
51+
4652
List<ScenePicture> renderedPictures = <ScenePicture>[];
53+
Map<ScenePicture, ui.Rect> clipRequests = <ScenePicture, ui.Rect>{};
4754
}
4855

4956
void testMain() {
@@ -149,4 +156,21 @@ void testMain() {
149156
expect(stubPictureRenderer.renderedPictures.first, pictures.first);
150157
expect(stubPictureRenderer.renderedPictures.last, pictures.last);
151158
});
159+
160+
test('SceneView clips pictures that are outside the window screen', () async {
161+
final StubPicture picture = StubPicture(const ui.Rect.fromLTWH(
162+
-50,
163+
-50,
164+
100,
165+
120,
166+
));
167+
168+
final EngineRootLayer rootLayer = EngineRootLayer();
169+
rootLayer.slices.add(PictureSlice(picture));
170+
final EngineScene scene = EngineScene(rootLayer);
171+
await sceneView.renderScene(scene, null);
172+
173+
expect(stubPictureRenderer.renderedPictures.length, 1);
174+
expect(stubPictureRenderer.clipRequests.containsKey(picture), true);
175+
});
152176
}

0 commit comments

Comments
 (0)