Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
);
});
});
}