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

Commit 509eaf9

Browse files
committed
[canvaskit] read pixels back in Picture.toImage
1 parent 77bc544 commit 509eaf9

File tree

3 files changed

+112
-14
lines changed

3 files changed

+112
-14
lines changed

lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ extension CanvasKitExtension on CanvasKit {
9292
double width,
9393
double height,
9494
);
95+
external SkSurface MakeRasterDirectSurface(
96+
SkImageInfo imageInfo,
97+
MallocObj pixels,
98+
double bytesPerRow,
99+
);
95100
external Uint8List getDataBytes(
96101
SkData skData,
97102
);
@@ -1277,6 +1282,9 @@ external _NativeType get _nativeFloat32ArrayType;
12771282
@JS('Uint32Array')
12781283
external _NativeType get _nativeUint32ArrayType;
12791284

1285+
@JS('Uint8Array')
1286+
external _NativeType get _nativeUint8ArrayType;
1287+
12801288
@JS('window.flutterCanvasKit.Malloc')
12811289
external Object _malloc(_NativeType nativeType, double length);
12821290

@@ -1296,6 +1304,10 @@ SkUint32List mallocUint32List(int length) {
12961304
return _malloc(_nativeUint32ArrayType, length.toDouble()) as SkUint32List;
12971305
}
12981306

1307+
SkUint8List mallocUint8List(int length) {
1308+
return _malloc(_nativeUint8ArrayType, length.toDouble()) as SkUint8List;
1309+
}
1310+
12991311
/// Frees the WASM memory occupied by a [SkFloat32List] or [SkUint32List].
13001312
///
13011313
/// The [list] is no longer usable after calling this function.
@@ -1354,6 +1366,20 @@ extension SkUint32ListExtension on SkUint32List {
13541366
external Uint32List toTypedArray();
13551367
}
13561368

1369+
/// Wraps a [Uint8List] backed by WASM memory.
1370+
///
1371+
/// This wrapper is necessary because the raw [Uint8List] will get detached
1372+
/// when WASM grows its memory. Call [toTypedArray] to get a new instance
1373+
/// that's attached to the current WASM memory block.
1374+
@JS()
1375+
@staticInterop
1376+
class SkUint8List extends MallocObj {}
1377+
1378+
extension SkUint8ListExtension on SkUint8List {
1379+
external double length;
1380+
external Uint8List toTypedArray();
1381+
}
1382+
13571383
/// Writes [color] information into the given [skColor] buffer.
13581384
Float32List _populateSkColor(SkFloat32List skColor, ui.Color color) {
13591385
final Float32List array = skColor.toTypedArray();

lib/web_ui/lib/src/engine/canvaskit/picture.dart

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,28 +100,42 @@ class CkPicture extends ManagedSkiaObject<SkPicture> implements ui.Picture {
100100
}
101101

102102
@override
103-
ui.Image toImageSync(int width, int height) {
104-
SurfaceFactory.instance.baseSurface.ensureSurface();
105-
if (SurfaceFactory.instance.baseSurface.usingSoftwareBackend) {
103+
CkImage toImageSync(int width, int height) {
104+
// TODO(yjbanov): toImageSyncGPU regressed Image.toByteData. See:
105+
// https://github.com/flutter/flutter/issues/121758
106+
// https://github.com/flutter/flutter/issues/117786#issuecomment-1456975886
107+
// SurfaceFactory.instance.baseSurface.ensureSurface();
108+
// if (SurfaceFactory.instance.baseSurface.usingSoftwareBackend) {
106109
return toImageSyncSoftware(width, height);
107-
}
108-
return toImageSyncGPU(width, height);
110+
// }
111+
// return toImageSyncGPU(width, height);
109112
}
110113

111-
ui.Image toImageSyncGPU(int width, int height) {
114+
CkImage toImageSyncGPU(int width, int height) {
112115
assert(debugCheckNotDisposed('Cannot convert picture to image.'));
113116

114-
final CkSurface ckSurface = SurfaceFactory.instance.baseSurface
115-
.createRenderTargetSurface(ui.Size(width.toDouble(), height.toDouble()));
116-
final CkCanvas ckCanvas = ckSurface.getCanvas();
117-
ckCanvas.clear(const ui.Color(0x00000000));
118-
ckCanvas.drawPicture(this);
119-
final SkImage skImage = ckSurface.surface.makeImageSnapshot();
120-
ckSurface.dispose();
117+
final SkUint8List memory = mallocUint8List(4 * width * height);
118+
final SkSurface skSurface = canvasKit.MakeRasterDirectSurface(
119+
SkImageInfo(
120+
width: width.toDouble(),
121+
height: height.toDouble(),
122+
colorType: canvasKit.ColorType.RGBA_8888,
123+
alphaType: canvasKit.AlphaType.Premul,
124+
colorSpace: SkColorSpaceSRGB,
125+
),
126+
memory,
127+
(4 * width).toDouble(),
128+
);
129+
final SkCanvas skCanvas = skSurface.getCanvas();
130+
skCanvas.clear(toSharedSkColor1(const ui.Color(0x00000000)));
131+
skCanvas.drawPicture(skiaObject);
132+
final SkImage skImage = skSurface.makeImageSnapshot();
133+
skSurface.dispose();
134+
free(memory);
121135
return CkImage(skImage);
122136
}
123137

124-
ui.Image toImageSyncSoftware(int width, int height) {
138+
CkImage toImageSyncSoftware(int width, int height) {
125139
assert(debugCheckNotDisposed('Cannot convert picture to image.'));
126140

127141
final Surface surface = SurfaceFactory.instance.pictureToImageSurface;

lib/web_ui/test/canvaskit/canvas_golden_test.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,64 @@ void testMain() {
828828
await matchGoldenFile('canvaskit_empty_scene.png',
829829
region: const ui.Rect.fromLTRB(0, 0, 100, 100));
830830
});
831+
832+
// Regression test for https://github.com/flutter/flutter/issues/121758
833+
test('resources used in temporary surfaces for Image.toByteData can cross to rendering overlays', () async {
834+
final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer;
835+
SurfaceFactory.instance.debugClear();
836+
837+
ui.platformViewRegistry.registerViewFactory(
838+
'test-platform-view',
839+
(int viewId) => createDomHTMLDivElement()..id = 'view-0',
840+
);
841+
await createPlatformView(0, 'test-platform-view');
842+
843+
CkPicture makeTextPicture(String text, ui.Offset offset) {
844+
final CkPictureRecorder recorder = CkPictureRecorder();
845+
final CkCanvas canvas = recorder.beginRecording(ui.Rect.largest);
846+
final CkParagraphBuilder builder = CkParagraphBuilder(CkParagraphStyle());
847+
builder.addText(text);
848+
final CkParagraph paragraph = builder.build();
849+
paragraph.layout(const ui.ParagraphConstraints(width: 100));
850+
canvas.drawRect(
851+
ui.Rect.fromLTWH(offset.dx, offset.dy, paragraph.width, paragraph.height).inflate(10),
852+
CkPaint()..color = const ui.Color(0xFF00FF00)
853+
);
854+
canvas.drawParagraph(paragraph, offset);
855+
return recorder.endRecording();
856+
}
857+
858+
CkPicture imageToPicture(CkImage image, ui.Offset offset) {
859+
final CkPictureRecorder recorder = CkPictureRecorder();
860+
final CkCanvas canvas = recorder.beginRecording(ui.Rect.largest);
861+
canvas.drawImage(image, offset, CkPaint());
862+
return recorder.endRecording();
863+
}
864+
865+
final CkPicture helloPicture = makeTextPicture('Hello', ui.Offset.zero);
866+
867+
final CkImage helloImage = helloPicture.toImageSync(100, 100);
868+
869+
// Calling toByteData is essential to hit the bug.
870+
await helloImage.toByteData(format: ui.ImageByteFormat.png);
871+
872+
final LayerSceneBuilder sb = LayerSceneBuilder();
873+
sb.pushOffset(0, 0);
874+
sb.addPicture(ui.Offset.zero, helloPicture);
875+
sb.addPlatformView(0, width: 10, height: 10);
876+
877+
// The image is rendered after the platform view so that it's rendered into
878+
// a separate surface, which is what triggers the bug. If the bug is present
879+
// the image will not appear on the UI.
880+
sb.addPicture(const ui.Offset(0, 50), imageToPicture(helloImage, ui.Offset.zero));
881+
sb.pop();
882+
883+
// The below line should not throw an error.
884+
rasterizer.draw(sb.build().layerTree);
885+
886+
await matchGoldenFile('cross_overlay_resources.png', region: const ui.Rect.fromLTRB(0, 0, 100, 100));
887+
});
888+
831889
// TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520
832890
}, skip: isSafari || isFirefox);
833891
}

0 commit comments

Comments
 (0)