blob: d7b24b4cba65d40ab2e81e9f962396c93bbce59e [file] [log] [blame]
// 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.
// @dart = 2.12
import 'dart:html' as html;
import 'dart:math' as math;
import 'dart:typed_data';
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 'package:web_engine_tester/golden_tester.dart';
import 'common.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
const ui.Rect region = const ui.Rect.fromLTRB(0, 0, 500, 250);
Future<void> matchPictureGolden(String goldenFile, CkPicture picture, { bool write = false }) async {
final EnginePlatformDispatcher dispatcher = ui.window.platformDispatcher as EnginePlatformDispatcher;
final LayerSceneBuilder sb = LayerSceneBuilder();
sb.pushOffset(0, 0);
sb.addPicture(ui.Offset.zero, picture);
dispatcher.rasterizer!.draw(sb.build().layerTree);
await matchGoldenFile(goldenFile, region: region, write: write);
}
void testMain() {
group('CkCanvas', () {
setUpCanvasKitTest();
test('renders using non-recording canvas if weak refs are supported', () async {
expect(browserSupportsFinalizationRegistry, isTrue,
reason: 'This test specifically tests non-recording canvas, which '
'only works if FinalizationRegistry is available.');
final CkPictureRecorder recorder = CkPictureRecorder();
final CkCanvas canvas = recorder.beginRecording(region);
expect(canvas.runtimeType, CkCanvas);
drawTestPicture(canvas);
await matchPictureGolden('canvaskit_picture_original.png', recorder.endRecording());
});
test('renders using a recording canvas if weak refs are not supported', () async {
browserSupportsFinalizationRegistry = false;
final CkPictureRecorder recorder = CkPictureRecorder();
final CkCanvas canvas = recorder.beginRecording(region);
expect(canvas, isA<RecordingCkCanvas>());
drawTestPicture(canvas);
final CkPicture originalPicture = recorder.endRecording();
await matchPictureGolden('canvaskit_picture_original.png', originalPicture);
final ByteData originalPixels = await (await originalPicture.toImage(50, 50)).toByteData() as ByteData;
// Test that a picture restored from a snapshot looks the same.
final CkPictureSnapshot? snapshot = canvas.pictureSnapshot;
expect(snapshot, isNotNull);
final SkPicture restoredSkPicture = snapshot!.toPicture();
expect(restoredSkPicture, isNotNull);
final CkPicture restoredPicture = CkPicture(restoredSkPicture, ui.Rect.fromLTRB(0, 0, 50, 50), snapshot);
final ByteData restoredPixels = await (await restoredPicture.toImage(50, 50)).toByteData() as ByteData;
await matchPictureGolden('canvaskit_picture_restored.png', restoredPicture);
expect(restoredPixels.buffer.asUint8List(), originalPixels.buffer.asUint8List());
});
// TODO: https://github.com/flutter/flutter/issues/60040
// TODO: https://github.com/flutter/flutter/issues/71520
}, skip: isIosSafari || isFirefox);
}
void drawTestPicture(CkCanvas canvas) {
canvas.clear(ui.Color(0xFFFFFFF));
canvas.translate(10, 10);
// Row 1
canvas.save();
canvas.save();
canvas.clipRect(
ui.Rect.fromLTRB(0, 0, 45, 45),
ui.ClipOp.intersect,
true,
);
canvas.clipRRect(
ui.RRect.fromLTRBR(5, 5, 50, 50, ui.Radius.circular(8)),
true,
);
canvas.clipPath(
CkPath()
..moveTo(5, 5)
..lineTo(25, 5)
..lineTo(45, 45)
..lineTo(5, 45)
..close(),
true,
);
canvas.drawColor(ui.Color.fromARGB(255, 100, 100, 0), ui.BlendMode.srcOver);
canvas.restore(); // remove clips
canvas.translate(60, 0);
canvas.drawCircle(
const ui.Offset(30, 25),
15,
CkPaint()..color = ui.Color(0xFF0000AA),
);
canvas.translate(60, 0);
canvas.drawArc(
ui.Rect.fromLTRB(10, 20, 50, 40),
math.pi / 4,
3 * math.pi / 2,
true,
CkPaint()..color = ui.Color(0xFF00AA00),
);
canvas.translate(60, 0);
canvas.drawImage(
generateTestImage(),
const ui.Offset(20, 20),
CkPaint(),
);
canvas.translate(60, 0);
final ui.RSTransform transform = ui.RSTransform.fromComponents(
rotation: 0,
scale: 1,
anchorX: 0,
anchorY: 0,
translateX: 0,
translateY: 0,
);
canvas.drawAtlasRaw(
CkPaint(),
generateTestImage(),
Float32List(4)
..[0] = transform.scos
..[1] = transform.ssin
..[2] = transform.tx + 20
..[3] = transform.ty + 20,
Float32List(4)
..[0] = 0
..[1] = 0
..[2] = 15
..[3] = 15,
[Float32List(4)],
ui.BlendMode.srcOver,
);
canvas.translate(60, 0);
canvas.drawDRRect(
ui.RRect.fromLTRBR(0, 0, 40, 30, ui.Radius.elliptical(16, 8)),
ui.RRect.fromLTRBR(10, 10, 30, 20, ui.Radius.elliptical(4, 8)),
CkPaint(),
);
canvas.translate(60, 0);
canvas.drawImageRect(
generateTestImage(),
ui.Rect.fromLTRB(0, 0, 15, 15),
ui.Rect.fromLTRB(10, 10, 40, 40),
CkPaint(),
);
canvas.translate(60, 0);
canvas.drawImageNine(
generateTestImage(),
ui.Rect.fromLTRB(5, 5, 15, 15),
ui.Rect.fromLTRB(10, 10, 50, 40),
CkPaint(),
);
canvas.restore();
// Row 2
canvas.translate(0, 60);
canvas.save();
canvas.drawLine(ui.Offset(0, 0), ui.Offset(40, 30), CkPaint());
canvas.translate(60, 0);
canvas.drawOval(
ui.Rect.fromLTRB(0, 0, 40, 30),
CkPaint(),
);
canvas.translate(60, 0);
canvas.save();
canvas.clipRect(ui.Rect.fromLTRB(0, 0, 50, 30), ui.ClipOp.intersect, true);
canvas.drawPaint(CkPaint()..color = ui.Color(0xFF6688AA));
canvas.restore();
canvas.translate(60, 0);
{
final CkPictureRecorder otherRecorder = CkPictureRecorder();
final CkCanvas otherCanvas =
otherRecorder.beginRecording(ui.Rect.fromLTRB(0, 0, 40, 20));
otherCanvas.drawCircle(
ui.Offset(30, 15),
10,
CkPaint()..color = ui.Color(0xFFAABBCC),
);
canvas.drawPicture(otherRecorder.endRecording());
}
canvas.translate(60, 0);
// TODO(yjbanov): CanvasKit.drawPoints is currently broken
// https://github.com/flutter/flutter/issues/71489
// But keeping this anyway as it's a good test-case that
// will ensure it's fixed when we have the fix.
canvas.drawPoints(
CkPaint()
..color = ui.Color(0xFF0000FF)
..strokeWidth = 5
..strokeCap = ui.StrokeCap.round,
ui.PointMode.polygon,
offsetListToFloat32List(<ui.Offset>[
ui.Offset(10, 10),
ui.Offset(20, 10),
ui.Offset(30, 20),
ui.Offset(40, 20)
]),
);
canvas.translate(60, 0);
canvas.drawRRect(
ui.RRect.fromLTRBR(0, 0, 40, 30, ui.Radius.circular(10)),
CkPaint(),
);
canvas.translate(60, 0);
canvas.drawRect(
ui.Rect.fromLTRB(0, 0, 40, 30),
CkPaint(),
);
canvas.translate(60, 0);
canvas.drawShadow(
CkPath()
..addRect(ui.Rect.fromLTRB(0, 0, 40, 30)),
ui.Color(0xFF00FF00),
4,
true,
);
canvas.restore();
// Row 3
canvas.translate(0, 60);
canvas.save();
canvas.drawVertices(
CkVertices(
ui.VertexMode.triangleFan,
<ui.Offset>[
ui.Offset(10, 30),
ui.Offset(30, 50),
ui.Offset(10, 60),
],
),
ui.BlendMode.srcOver,
CkPaint(),
);
canvas.translate(60, 0);
final int restorePoint = canvas.save();
for (int i = 0; i < 5; i++) {
canvas.save();
canvas.translate(10, 10);
canvas.drawCircle(ui.Offset.zero, 5, CkPaint());
}
canvas.restoreToCount(restorePoint);
canvas.drawCircle(ui.Offset.zero, 7, CkPaint()..color = ui.Color(0xFFFF0000));
canvas.translate(60, 0);
canvas.drawLine(ui.Offset.zero, ui.Offset(30, 30), CkPaint());
canvas.save();
canvas.rotate(-math.pi / 8);
canvas.drawLine(ui.Offset.zero, ui.Offset(30, 30), CkPaint());
canvas.drawCircle(ui.Offset(30, 30), 7, CkPaint()..color = ui.Color(0xFF00AA00));
canvas.restore();
canvas.translate(60, 0);
final CkPaint thickStroke = CkPaint()
..style = ui.PaintingStyle.stroke
..strokeWidth = 20;
final CkPaint semitransparent = CkPaint()..color = ui.Color(0x66000000);
canvas.saveLayer(region, semitransparent);
canvas.drawLine(ui.Offset(10, 10), ui.Offset(50, 50), thickStroke);
canvas.drawLine(ui.Offset(50, 10), ui.Offset(10, 50), thickStroke);
canvas.restore();
canvas.translate(60, 0);
canvas.saveLayerWithoutBounds(semitransparent);
canvas.drawLine(ui.Offset(10, 10), ui.Offset(50, 50), thickStroke);
canvas.drawLine(ui.Offset(50, 10), ui.Offset(10, 50), thickStroke);
canvas.restore();
// To test saveLayerWithFilter we draw three circles with only the middle one
// blurred using the layer image filter.
canvas.translate(60, 0);
canvas.saveLayer(region, CkPaint());
canvas.drawCircle(ui.Offset(30, 30), 10, CkPaint());
{
canvas.saveLayerWithFilter(region, ui.ImageFilter.blur(sigmaX: 5, sigmaY: 10));
canvas.drawCircle(ui.Offset(10, 10), 10, CkPaint());
canvas.drawCircle(ui.Offset(50, 50), 10, CkPaint());
canvas.restore();
}
canvas.restore();
canvas.translate(60, 0);
canvas.save();
canvas.translate(30, 30);
canvas.scale(2, 1.5);
canvas.drawCircle(ui.Offset.zero, 10, CkPaint());
canvas.restore();
canvas.translate(60, 0);
canvas.save();
canvas.translate(30, 30);
canvas.skew(2, 1.5);
canvas.drawRect(ui.Rect.fromLTRB(-10, -10, 10, 10), CkPaint());
canvas.restore();
canvas.restore();
// Row 4
canvas.translate(0, 60);
canvas.save();
canvas.save();
final Matrix4 matrix = Matrix4.identity();
matrix.translate(30, 30);
matrix.scale(2, 1.5);
canvas.transform(matrix.storage);
canvas.drawCircle(ui.Offset.zero, 10, CkPaint());
canvas.restore();
canvas.translate(60, 0);
final CkParagraphBuilder pb = CkParagraphBuilder(CkParagraphStyle(
fontFamily: 'Roboto',
fontStyle: ui.FontStyle.normal,
fontWeight: ui.FontWeight.normal,
fontSize: 18,
));
pb.pushStyle(CkTextStyle(
color: ui.Color(0xFF0000AA),
));
pb.addText('Hello');
pb.pop();
final CkParagraph p = pb.build();
p.layout(ui.ParagraphConstraints(width: 1000));
canvas.drawParagraph(
p,
ui.Offset(10, 20),
);
canvas.translate(60, 0);
canvas.drawPath(
CkPath()
..moveTo(30, 20)
..lineTo(50, 50)
..lineTo(10, 50)
..close(),
CkPaint()..color = ui.Color(0xFF0000AA),
);
canvas.restore();
}
CkImage generateTestImage() {
final html.CanvasElement canvas = html.CanvasElement()
..width = 20
..height = 20;
final html.CanvasRenderingContext2D ctx = canvas.context2D;
ctx.fillStyle = '#FF0000';
ctx.fillRect(0, 0, 10, 10);
ctx.fillStyle = '#00FF00';
ctx.fillRect(0, 10, 10, 10);
ctx.fillStyle = '#0000FF';
ctx.fillRect(10, 0, 10, 10);
ctx.fillStyle = '#FF00FF';
ctx.fillRect(10, 10, 10, 10);
final Uint8List imageData = ctx.getImageData(0, 0, 20, 20).data.buffer.asUint8List();
final SkImage skImage = canvasKit.MakeImage(
imageData,
20,
20,
canvasKit.AlphaType.Premul,
canvasKit.ColorType.RGBA_8888,
SkColorSpaceSRGB,
);
return CkImage(skImage);
}