diff --git a/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart index 7793cfbe6cbce..9c444929ea431 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart @@ -53,6 +53,9 @@ class RenderCanvas { late final DomCanvasRenderingContextBitmapRenderer renderContext = canvasElement.contextBitmapRenderer; + late final DomCanvasRenderingContext2D renderContext2d = + canvasElement.context2D; + double _currentDevicePixelRatio = -1; /// Sets the CSS size of the canvas so that canvas pixels are 1:1 with device @@ -82,6 +85,25 @@ class RenderCanvas { renderContext.transferFromImageBitmap(bitmap); } + void renderWithNoBitmapSupport( + DomCanvasImageSource imageSource, + int sourceHeight, + ui.Size size, + ) { + _ensureSize(size); + renderContext2d.drawImage( + imageSource, + 0, + sourceHeight - size.height, + size.width, + size.height, + 0, + 0, + size.width, + size.height, + ); + } + /// Ensures that this canvas can draw a frame of the given [size]. void _ensureSize(ui.Size size) { // Check if the frame is the same size as before, and if so, we don't need diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index a479005c0eeed..f86998f43f421 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -114,23 +114,35 @@ class Surface { pictures.forEach(skCanvas.drawPicture); _surface!.flush(); - DomImageBitmap bitmap; - if (Surface.offscreenCanvasSupported) { - bitmap = (await createImageBitmap(_offscreenCanvas! as JSObject, ( - x: 0, - y: _pixelHeight - frameSize.height.toInt(), - width: frameSize.width.toInt(), - height: frameSize.height.toInt(), - )).toDart)! as DomImageBitmap; + if (browserSupportsCreateImageBitmap) { + DomImageBitmap bitmap; + if (Surface.offscreenCanvasSupported) { + bitmap = (await createImageBitmap(_offscreenCanvas! as JSObject, ( + x: 0, + y: _pixelHeight - frameSize.height.toInt(), + width: frameSize.width.toInt(), + height: frameSize.height.toInt(), + )).toDart)! as DomImageBitmap; + } else { + bitmap = (await createImageBitmap(_canvasElement! as JSObject, ( + x: 0, + y: _pixelHeight - frameSize.height.toInt(), + width: frameSize.width.toInt(), + height: frameSize.height.toInt() + )).toDart)! as DomImageBitmap; + } + canvas.render(bitmap); } else { - bitmap = (await createImageBitmap(_canvasElement! as JSObject, ( - x: 0, - y: _pixelHeight - frameSize.height.toInt(), - width: frameSize.width.toInt(), - height: frameSize.height.toInt() - )).toDart)! as DomImageBitmap; + // If the browser doesn't support `createImageBitmap` (e.g. Safari 14) + // then render using `drawImage` instead. + DomCanvasImageSource imageSource; + if (Surface.offscreenCanvasSupported) { + imageSource = _offscreenCanvas! as DomCanvasImageSource; + } else { + imageSource = _canvasElement! as DomCanvasImageSource; + } + canvas.renderWithNoBitmapSupport(imageSource, _pixelHeight, frameSize); } - canvas.render(bitmap); } /// Acquire a frame of the given [size] containing a drawable canvas. diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index 25030af7a8c65..284cd7296832a 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -1226,10 +1226,53 @@ extension DomCanvasRenderingContext2DExtension on DomCanvasRenderingContext2D { x0.toJS, y0.toJS, r0.toJS, x1.toJS, y1.toJS, r1.toJS); @JS('drawImage') - external JSVoid _drawImage( - DomCanvasImageSource source, JSNumber destX, JSNumber destY); - void drawImage(DomCanvasImageSource source, num destX, num destY) => - _drawImage(source, destX.toJS, destY.toJS); + external JSVoid _drawImage1( + DomCanvasImageSource source, JSNumber dx, JSNumber dy); + @JS('drawImage') + external JSVoid _drawImage2( + DomCanvasImageSource source, + JSNumber sx, + JSNumber sy, + JSNumber sWidth, + JSNumber sHeight, + JSNumber dx, + JSNumber dy, + JSNumber dWidth, + JSNumber dHeight, + ); + void drawImage( + DomCanvasImageSource source, + num srcxOrDstX, + num srcyOrDstY, [ + num? srcWidth, + num? srcHeight, + num? dstX, + num? dstY, + num? dstWidth, + num? dstHeight, + ]) { + if (srcWidth == null) { + // In this case the numbers provided are the destination x and y offset. + return _drawImage1(source, srcxOrDstX.toJS, srcyOrDstY.toJS); + } else { + assert(srcHeight != null && + dstX != null && + dstY != null && + dstWidth != null && + dstHeight != null); + return _drawImage2( + source, + srcxOrDstX.toJS, + srcyOrDstY.toJS, + srcWidth.toJS, + srcHeight!.toJS, + dstX!.toJS, + dstY!.toJS, + dstWidth!.toJS, + dstHeight!.toJS, + ); + } + } @JS('fill') external JSVoid _fill1(); @@ -3623,6 +3666,15 @@ external JSAny? get _offscreenCanvasConstructor; bool browserSupportsOffscreenCanvas = _offscreenCanvasConstructor != null; +@JS('window.createImageBitmap') +external JSAny? get _createImageBitmapFunction; + +/// Set to `true` to disable `createImageBitmap` support. Used in tests. +bool debugDisableCreateImageBitmapSupport = false; + +bool browserSupportsCreateImageBitmap = + !debugDisableCreateImageBitmapSupport || _createImageBitmapFunction != null; + @JS() @staticInterop extension JSArrayExtension on JSArray { diff --git a/lib/web_ui/test/canvaskit/no_create_image_bitmap_test.dart b/lib/web_ui/test/canvaskit/no_create_image_bitmap_test.dart new file mode 100644 index 0000000000000..4ee6b09aa3a2f --- /dev/null +++ b/lib/web_ui/test/canvaskit/no_create_image_bitmap_test.dart @@ -0,0 +1,65 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +import 'common.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +const ui.Rect region = ui.Rect.fromLTRB(0, 0, 500, 250); + +/// Test that we can render even if `createImageBitmap` is not supported. +void testMain() { + group('CanvasKit', () { + setUpCanvasKitTest(); + setUp(() async { + EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(1.0); + debugDisableCreateImageBitmapSupport = true; + }); + + tearDown(() { + debugDisableCreateImageBitmapSupport = false; + }); + + test('can render without createImageBitmap', () async { + final CkPictureRecorder recorder = CkPictureRecorder(); + final CkCanvas canvas = recorder.beginRecording(region); + + final CkGradientLinear gradient = CkGradientLinear( + ui.Offset(region.left + region.width / 4, region.height / 2), + ui.Offset(region.right - region.width / 8, region.height / 2), + const [ + ui.Color(0xFF4285F4), + ui.Color(0xFF34A853), + ui.Color(0xFFFBBC05), + ui.Color(0xFFEA4335), + ui.Color(0xFF4285F4), + ], + const [ + 0.0, + 0.25, + 0.5, + 0.75, + 1.0, + ], + ui.TileMode.clamp, + null); + + final CkPaint paint = CkPaint()..shader = gradient; + + canvas.drawRect(region, paint); + + await matchPictureGolden( + 'canvaskit_linear_gradient_no_create_image_bitmap.png', + recorder.endRecording(), + region: region, + ); + }); + }); +}