Skip to content

Commit

Permalink
[canvaskit] Fall back to drawImage for browsers that don't support …
Browse files Browse the repository at this point in the history
…`createImageBitmap` (flutter#48336)

Safari 14 doesn't have the `createImageBitmap` API available. This
change allows us to render into `RenderCanvas` without using
`createImageBitmap` in that case.

Fixes flutter/flutter#138910

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide] and the [C++,
Objective-C, Java style guides].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making or feature I am
adding, or the PR is [test-exempt]. See [testing the engine] for
instructions on writing and running engine tests.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I signed the [CLA].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
  • Loading branch information
harryterkelsen authored Nov 28, 2023
1 parent fc2ab99 commit 96137d0
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 19 deletions.
22 changes: 22 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/render_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
42 changes: 27 additions & 15 deletions lib/web_ui/lib/src/engine/canvaskit/surface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
60 changes: 56 additions & 4 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down
65 changes: 65 additions & 0 deletions lib/web_ui/test/canvaskit/no_create_image_bitmap_test.dart
Original file line number Diff line number Diff line change
@@ -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>[
ui.Color(0xFF4285F4),
ui.Color(0xFF34A853),
ui.Color(0xFFFBBC05),
ui.Color(0xFFEA4335),
ui.Color(0xFF4285F4),
],
const <double>[
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,
);
});
});
}

0 comments on commit 96137d0

Please sign in to comment.