[web] Remove HTML from the engine's test suites (#162404)
- Remove html bundles/suites from configs and CI. - Delete html test files. - Replace the `isHtml` helper with `false` and refactor accordingly.
This commit is contained in:
@@ -2,50 +2,6 @@
|
||||
"_comment": "THIS IS A GENERATED FILE. Do not edit this file directly.",
|
||||
"_comment2": "See `generate_builder_json.dart` for the generator code",
|
||||
"builds": [
|
||||
{
|
||||
"name": "web_tests/test_bundles/dart2js-html-html",
|
||||
"drone_dimensions": [
|
||||
"device_type=none",
|
||||
"os=Linux"
|
||||
],
|
||||
"generators": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "compile bundle dart2js-html-html",
|
||||
"parameters": [
|
||||
"test",
|
||||
"--compile",
|
||||
"--bundle=dart2js-html-html"
|
||||
],
|
||||
"scripts": [
|
||||
"flutter/lib/web_ui/dev/felt"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "web_tests/test_bundles/dart2js-html-ui",
|
||||
"drone_dimensions": [
|
||||
"device_type=none",
|
||||
"os=Linux"
|
||||
],
|
||||
"generators": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "compile bundle dart2js-html-ui",
|
||||
"parameters": [
|
||||
"test",
|
||||
"--compile",
|
||||
"--bundle=dart2js-html-ui"
|
||||
],
|
||||
"scripts": [
|
||||
"flutter/lib/web_ui/dev/felt"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "web_tests/test_bundles/dart2js-canvaskit-engine",
|
||||
"drone_dimensions": [
|
||||
@@ -209,8 +165,6 @@
|
||||
"download_jdk": false
|
||||
},
|
||||
"dependencies": [
|
||||
"web_tests/test_bundles/dart2js-html-html",
|
||||
"web_tests/test_bundles/dart2js-html-ui",
|
||||
"web_tests/test_bundles/dart2js-canvaskit-engine",
|
||||
"web_tests/test_bundles/dart2js-canvaskit-canvaskit",
|
||||
"web_tests/test_bundles/dart2js-canvaskit-ui",
|
||||
@@ -234,27 +188,19 @@
|
||||
"parameters": [
|
||||
"test",
|
||||
"--copy-artifacts",
|
||||
"--suite=chrome-dart2js-html-html",
|
||||
"--suite=chrome-dart2js-html-ui",
|
||||
"--suite=chrome-dart2js-canvaskit-engine",
|
||||
"--suite=chrome-dart2js-canvaskit-canvaskit",
|
||||
"--suite=chrome-dart2js-canvaskit-ui",
|
||||
"--suite=chrome-full-dart2js-canvaskit-canvaskit",
|
||||
"--suite=chrome-full-dart2js-canvaskit-ui",
|
||||
"--suite=edge-dart2js-html-html",
|
||||
"--suite=edge-dart2js-html-ui",
|
||||
"--suite=edge-dart2js-canvaskit-engine",
|
||||
"--suite=edge-dart2js-canvaskit-canvaskit",
|
||||
"--suite=edge-dart2js-canvaskit-ui",
|
||||
"--suite=edge-full-dart2js-canvaskit-canvaskit",
|
||||
"--suite=edge-full-dart2js-canvaskit-ui",
|
||||
"--suite=firefox-dart2js-html-html",
|
||||
"--suite=firefox-dart2js-html-ui",
|
||||
"--suite=firefox-dart2js-canvaskit-engine",
|
||||
"--suite=firefox-dart2js-canvaskit-canvaskit",
|
||||
"--suite=firefox-dart2js-canvaskit-ui",
|
||||
"--suite=safari-dart2js-html-html",
|
||||
"--suite=safari-dart2js-html-ui",
|
||||
"--suite=safari-dart2js-canvaskit-engine",
|
||||
"--suite=safari-dart2js-canvaskit-canvaskit",
|
||||
"--suite=safari-dart2js-canvaskit-ui",
|
||||
@@ -269,24 +215,6 @@
|
||||
],
|
||||
"script": "flutter/lib/web_ui/dev/felt"
|
||||
},
|
||||
{
|
||||
"name": "run suite chrome-dart2js-html-html",
|
||||
"parameters": [
|
||||
"test",
|
||||
"--run",
|
||||
"--suite=chrome-dart2js-html-html"
|
||||
],
|
||||
"script": "flutter/lib/web_ui/dev/felt"
|
||||
},
|
||||
{
|
||||
"name": "run suite chrome-dart2js-html-ui",
|
||||
"parameters": [
|
||||
"test",
|
||||
"--run",
|
||||
"--suite=chrome-dart2js-html-ui"
|
||||
],
|
||||
"script": "flutter/lib/web_ui/dev/felt"
|
||||
},
|
||||
{
|
||||
"name": "run suite chrome-dart2js-canvaskit-engine",
|
||||
"parameters": [
|
||||
@@ -400,8 +328,6 @@
|
||||
"download_jdk": false
|
||||
},
|
||||
"dependencies": [
|
||||
"web_tests/test_bundles/dart2js-html-html",
|
||||
"web_tests/test_bundles/dart2js-html-ui",
|
||||
"web_tests/test_bundles/dart2js-canvaskit-engine",
|
||||
"web_tests/test_bundles/dart2js-canvaskit-canvaskit",
|
||||
"web_tests/test_bundles/dart2js-canvaskit-ui",
|
||||
@@ -423,27 +349,19 @@
|
||||
"parameters": [
|
||||
"test",
|
||||
"--copy-artifacts",
|
||||
"--suite=chrome-dart2js-html-html",
|
||||
"--suite=chrome-dart2js-html-ui",
|
||||
"--suite=chrome-dart2js-canvaskit-engine",
|
||||
"--suite=chrome-dart2js-canvaskit-canvaskit",
|
||||
"--suite=chrome-dart2js-canvaskit-ui",
|
||||
"--suite=chrome-full-dart2js-canvaskit-canvaskit",
|
||||
"--suite=chrome-full-dart2js-canvaskit-ui",
|
||||
"--suite=edge-dart2js-html-html",
|
||||
"--suite=edge-dart2js-html-ui",
|
||||
"--suite=edge-dart2js-canvaskit-engine",
|
||||
"--suite=edge-dart2js-canvaskit-canvaskit",
|
||||
"--suite=edge-dart2js-canvaskit-ui",
|
||||
"--suite=edge-full-dart2js-canvaskit-canvaskit",
|
||||
"--suite=edge-full-dart2js-canvaskit-ui",
|
||||
"--suite=firefox-dart2js-html-html",
|
||||
"--suite=firefox-dart2js-html-ui",
|
||||
"--suite=firefox-dart2js-canvaskit-engine",
|
||||
"--suite=firefox-dart2js-canvaskit-canvaskit",
|
||||
"--suite=firefox-dart2js-canvaskit-ui",
|
||||
"--suite=safari-dart2js-html-html",
|
||||
"--suite=safari-dart2js-html-ui",
|
||||
"--suite=safari-dart2js-canvaskit-engine",
|
||||
"--suite=safari-dart2js-canvaskit-canvaskit",
|
||||
"--suite=safari-dart2js-canvaskit-ui",
|
||||
@@ -458,24 +376,6 @@
|
||||
],
|
||||
"script": "flutter/lib/web_ui/dev/felt"
|
||||
},
|
||||
{
|
||||
"name": "run suite firefox-dart2js-html-html",
|
||||
"parameters": [
|
||||
"test",
|
||||
"--run",
|
||||
"--suite=firefox-dart2js-html-html"
|
||||
],
|
||||
"script": "flutter/lib/web_ui/dev/felt"
|
||||
},
|
||||
{
|
||||
"name": "run suite firefox-dart2js-html-ui",
|
||||
"parameters": [
|
||||
"test",
|
||||
"--run",
|
||||
"--suite=firefox-dart2js-html-ui"
|
||||
],
|
||||
"script": "flutter/lib/web_ui/dev/felt"
|
||||
},
|
||||
{
|
||||
"name": "run suite firefox-dart2js-canvaskit-engine",
|
||||
"parameters": [
|
||||
@@ -527,8 +427,6 @@
|
||||
"download_jdk": false
|
||||
},
|
||||
"dependencies": [
|
||||
"web_tests/test_bundles/dart2js-html-html",
|
||||
"web_tests/test_bundles/dart2js-html-ui",
|
||||
"web_tests/test_bundles/dart2js-canvaskit-engine",
|
||||
"web_tests/test_bundles/dart2js-canvaskit-canvaskit",
|
||||
"web_tests/test_bundles/dart2js-canvaskit-ui",
|
||||
@@ -546,27 +444,19 @@
|
||||
"parameters": [
|
||||
"test",
|
||||
"--copy-artifacts",
|
||||
"--suite=chrome-dart2js-html-html",
|
||||
"--suite=chrome-dart2js-html-ui",
|
||||
"--suite=chrome-dart2js-canvaskit-engine",
|
||||
"--suite=chrome-dart2js-canvaskit-canvaskit",
|
||||
"--suite=chrome-dart2js-canvaskit-ui",
|
||||
"--suite=chrome-full-dart2js-canvaskit-canvaskit",
|
||||
"--suite=chrome-full-dart2js-canvaskit-ui",
|
||||
"--suite=edge-dart2js-html-html",
|
||||
"--suite=edge-dart2js-html-ui",
|
||||
"--suite=edge-dart2js-canvaskit-engine",
|
||||
"--suite=edge-dart2js-canvaskit-canvaskit",
|
||||
"--suite=edge-dart2js-canvaskit-ui",
|
||||
"--suite=edge-full-dart2js-canvaskit-canvaskit",
|
||||
"--suite=edge-full-dart2js-canvaskit-ui",
|
||||
"--suite=firefox-dart2js-html-html",
|
||||
"--suite=firefox-dart2js-html-ui",
|
||||
"--suite=firefox-dart2js-canvaskit-engine",
|
||||
"--suite=firefox-dart2js-canvaskit-canvaskit",
|
||||
"--suite=firefox-dart2js-canvaskit-ui",
|
||||
"--suite=safari-dart2js-html-html",
|
||||
"--suite=safari-dart2js-html-ui",
|
||||
"--suite=safari-dart2js-canvaskit-engine",
|
||||
"--suite=safari-dart2js-canvaskit-canvaskit",
|
||||
"--suite=safari-dart2js-canvaskit-ui",
|
||||
@@ -581,24 +471,6 @@
|
||||
],
|
||||
"script": "flutter/lib/web_ui/dev/felt"
|
||||
},
|
||||
{
|
||||
"name": "run suite safari-dart2js-html-html",
|
||||
"parameters": [
|
||||
"test",
|
||||
"--run",
|
||||
"--suite=safari-dart2js-html-html"
|
||||
],
|
||||
"script": "flutter/lib/web_ui/dev/felt"
|
||||
},
|
||||
{
|
||||
"name": "run suite safari-dart2js-html-ui",
|
||||
"parameters": [
|
||||
"test",
|
||||
"--run",
|
||||
"--suite=safari-dart2js-html-ui"
|
||||
],
|
||||
"script": "flutter/lib/web_ui/dev/felt"
|
||||
},
|
||||
{
|
||||
"name": "run suite safari-dart2js-canvaskit-engine",
|
||||
"parameters": [
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:yaml/yaml.dart';
|
||||
|
||||
enum Compiler { dart2js, dart2wasm }
|
||||
|
||||
enum Renderer { html, canvaskit, skwasm }
|
||||
enum Renderer { canvaskit, skwasm }
|
||||
|
||||
class CompileConfiguration {
|
||||
CompileConfiguration(this.name, this.compiler, this.renderer);
|
||||
|
||||
@@ -512,3 +512,17 @@ class HtmlPatternMatcher extends Matcher {
|
||||
return mismatchDescription;
|
||||
}
|
||||
}
|
||||
|
||||
Matcher listEqual(List<int> source, {int tolerance = 0}) {
|
||||
return predicate((List<int> target) {
|
||||
if (source.length != target.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < source.length; i += 1) {
|
||||
if ((source[i] - target[i]).abs() > tolerance) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, source.toString());
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import '../../common/matchers.dart';
|
||||
import '../../html/image_test.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => doTests);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
# See the `README.md` in this directory for documentation on the structure of
|
||||
# this file.
|
||||
compile-configs:
|
||||
- name: dart2js-html
|
||||
compiler: dart2js
|
||||
renderer: html
|
||||
|
||||
- name: dart2js-canvaskit
|
||||
compiler: dart2js
|
||||
renderer: canvaskit
|
||||
@@ -30,10 +26,6 @@ test-sets:
|
||||
- name: canvaskit
|
||||
directory: canvaskit
|
||||
|
||||
# Tests for html-renderer-specific functionality
|
||||
- name: html
|
||||
directory: html
|
||||
|
||||
# Tests for renderer functionality that can be run on any renderer
|
||||
- name: ui
|
||||
directory: ui
|
||||
@@ -43,14 +35,6 @@ test-sets:
|
||||
directory: fallbacks
|
||||
|
||||
test-bundles:
|
||||
- name: dart2js-html-html
|
||||
test-set: html
|
||||
compile-configs: dart2js-html
|
||||
|
||||
- name: dart2js-html-ui
|
||||
test-set: ui
|
||||
compile-configs: dart2js-html
|
||||
|
||||
- name: dart2js-canvaskit-engine
|
||||
test-set: engine
|
||||
compile-configs: dart2js-canvaskit
|
||||
@@ -114,14 +98,6 @@ run-configs:
|
||||
canvaskit-variant: full
|
||||
|
||||
test-suites:
|
||||
- name: chrome-dart2js-html-html
|
||||
test-bundle: dart2js-html-html
|
||||
run-config: chrome
|
||||
|
||||
- name: chrome-dart2js-html-ui
|
||||
test-bundle: dart2js-html-ui
|
||||
run-config: chrome
|
||||
|
||||
- name: chrome-dart2js-canvaskit-engine
|
||||
test-bundle: dart2js-canvaskit-engine
|
||||
run-config: chrome
|
||||
@@ -147,14 +123,6 @@ test-suites:
|
||||
run-config: chrome-full
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: edge-dart2js-html-html
|
||||
test-bundle: dart2js-html-html
|
||||
run-config: edge
|
||||
|
||||
- name: edge-dart2js-html-ui
|
||||
test-bundle: dart2js-html-ui
|
||||
run-config: edge
|
||||
|
||||
- name: edge-dart2js-canvaskit-engine
|
||||
test-bundle: dart2js-canvaskit-engine
|
||||
run-config: edge
|
||||
@@ -180,14 +148,6 @@ test-suites:
|
||||
run-config: edge-full
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: firefox-dart2js-html-html
|
||||
test-bundle: dart2js-html-html
|
||||
run-config: firefox
|
||||
|
||||
- name: firefox-dart2js-html-ui
|
||||
test-bundle: dart2js-html-ui
|
||||
run-config: firefox
|
||||
|
||||
- name: firefox-dart2js-canvaskit-engine
|
||||
test-bundle: dart2js-canvaskit-engine
|
||||
run-config: firefox
|
||||
@@ -203,14 +163,6 @@ test-suites:
|
||||
run-config: firefox
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: safari-dart2js-html-html
|
||||
test-bundle: dart2js-html-html
|
||||
run-config: safari
|
||||
|
||||
- name: safari-dart2js-html-ui
|
||||
test-bundle: dart2js-html-ui
|
||||
run-config: safari
|
||||
|
||||
- name: safari-dart2js-canvaskit-engine
|
||||
test-bundle: dart2js-canvaskit-engine
|
||||
run-config: safari
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
// 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 'dart:math' as math;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
import '../common/test_initialization.dart';
|
||||
import 'paragraph/helper.dart';
|
||||
|
||||
DomElement get sceneHost =>
|
||||
EnginePlatformDispatcher.instance.implicitView!.dom.renderingHost.querySelector(
|
||||
DomManager.sceneHostTagName,
|
||||
)!;
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 500, 100);
|
||||
|
||||
late BitmapCanvas canvas;
|
||||
|
||||
void appendToScene() {
|
||||
// Create a <flt-scene> element to make sure our CSS reset applies correctly.
|
||||
final DomElement testScene = createDomElement('flt-scene');
|
||||
if (isIosSafari) {
|
||||
// Shrink to fit on the iPhone screen.
|
||||
testScene.style.position = 'absolute';
|
||||
testScene.style.transformOrigin = '0 0 0';
|
||||
testScene.style.transform = 'scale(0.3)';
|
||||
}
|
||||
testScene.append(canvas.rootElement);
|
||||
sceneHost.append(testScene);
|
||||
}
|
||||
|
||||
setUpUnitTests(
|
||||
withImplicitView: true,
|
||||
emulateTesterEnvironment: false,
|
||||
setUpTestViewDimensions: false,
|
||||
);
|
||||
|
||||
tearDown(() {
|
||||
sceneHost.querySelector('flt-scene')?.remove();
|
||||
});
|
||||
|
||||
/// Draws several lines, some aligned precisely with the pixel grid, and some
|
||||
/// that are offset by 0.5 vertically or horizontally.
|
||||
///
|
||||
/// The produced picture stresses the antialiasing generated by the browser
|
||||
/// when positioning and rasterizing `<canvas>` tags. Aliasing artifacts can
|
||||
/// be seen depending on pixel alignment and whether antialiasing happens
|
||||
/// before or after rasterization.
|
||||
void drawMisalignedLines(BitmapCanvas canvas) {
|
||||
final SurfacePaintData linePaint =
|
||||
(SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1)
|
||||
.paintData;
|
||||
|
||||
final SurfacePaintData fillPaint = (SurfacePaint()..style = PaintingStyle.fill).paintData;
|
||||
|
||||
canvas.translate(10, 10);
|
||||
|
||||
canvas.drawRect(const Rect.fromLTWH(0, 0, 40, 40), linePaint);
|
||||
|
||||
canvas.drawLine(const Offset(10, 0), const Offset(10, 40), linePaint);
|
||||
|
||||
canvas.drawLine(const Offset(20.5, 0), const Offset(20, 40), linePaint);
|
||||
|
||||
canvas.drawCircle(const Offset(30, 10), 3, fillPaint);
|
||||
canvas.drawCircle(const Offset(30.5, 30), 3, fillPaint);
|
||||
}
|
||||
|
||||
test('renders pixels that are not aligned inside the canvas', () async {
|
||||
canvas = BitmapCanvas(const Rect.fromLTWH(0, 0, 60, 60), RenderStrategy());
|
||||
|
||||
drawMisalignedLines(canvas);
|
||||
|
||||
appendToScene();
|
||||
|
||||
await matchGoldenFile('misaligned_pixels_in_canvas_test.png', region: region);
|
||||
});
|
||||
|
||||
test('compensates for misalignment of the canvas', () async {
|
||||
// Notice the 0.5 offset in the bounds rectangle. It's what causes the
|
||||
// misalignment of canvas relative to the pixel grid. BitmapCanvas will
|
||||
// shift its position back to 0.0 and at the same time it will it will
|
||||
// compensate by shifting the contents of the canvas in the opposite
|
||||
// direction.
|
||||
canvas = BitmapCanvas(const Rect.fromLTWH(0.5, 0.5, 60, 60), RenderStrategy());
|
||||
canvas.clipRect(const Rect.fromLTWH(0, 0, 50, 50), ClipOp.intersect);
|
||||
drawMisalignedLines(canvas);
|
||||
|
||||
appendToScene();
|
||||
|
||||
await matchGoldenFile('misaligned_canvas_test.png', region: region);
|
||||
});
|
||||
|
||||
test('fill the whole canvas with color even when transformed', () async {
|
||||
canvas = BitmapCanvas(const Rect.fromLTWH(0, 0, 50, 50), RenderStrategy());
|
||||
canvas.clipRect(const Rect.fromLTWH(0, 0, 50, 50), ClipOp.intersect);
|
||||
canvas.translate(25, 25);
|
||||
canvas.drawColor(const Color.fromRGBO(0, 255, 0, 1.0), BlendMode.src);
|
||||
|
||||
appendToScene();
|
||||
|
||||
await matchGoldenFile('bitmap_canvas_fills_color_when_transformed.png', region: region);
|
||||
});
|
||||
|
||||
test('fill the whole canvas with paint even when transformed', () async {
|
||||
canvas = BitmapCanvas(const Rect.fromLTWH(0, 0, 50, 50), RenderStrategy());
|
||||
canvas.clipRect(const Rect.fromLTWH(0, 0, 50, 50), ClipOp.intersect);
|
||||
canvas.translate(25, 25);
|
||||
canvas.drawPaint(
|
||||
SurfacePaintData()
|
||||
..color = const Color.fromRGBO(0, 255, 0, 1.0).value
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
|
||||
appendToScene();
|
||||
|
||||
await matchGoldenFile('bitmap_canvas_fills_paint_when_transformed.png', region: region);
|
||||
});
|
||||
|
||||
// This test reproduces text blurriness when two pieces of text appear inside
|
||||
// two nested clips:
|
||||
//
|
||||
// ┌───────────────────────┐
|
||||
// │ text in outer clip │
|
||||
// │ ┌────────────────────┐│
|
||||
// │ │ text in inner clip ││
|
||||
// │ └────────────────────┘│
|
||||
// └───────────────────────┘
|
||||
//
|
||||
// This test clips using canvas. See a similar test in `compositing_golden_test.dart`,
|
||||
// which clips using layers.
|
||||
//
|
||||
// More details: https://github.com/flutter/flutter/issues/32274
|
||||
test('renders clipped DOM text with high quality', () async {
|
||||
final CanvasParagraph paragraph =
|
||||
(ParagraphBuilder(ParagraphStyle(fontFamily: 'Roboto'))..addText('Am I blurry?')).build()
|
||||
as CanvasParagraph;
|
||||
paragraph.layout(const ParagraphConstraints(width: 1000));
|
||||
|
||||
final Rect canvasSize = Rect.fromLTRB(
|
||||
0,
|
||||
0,
|
||||
paragraph.maxIntrinsicWidth + 16,
|
||||
2 * paragraph.height + 32,
|
||||
);
|
||||
final Rect outerClip = Rect.fromLTRB(0.5, 0.5, canvasSize.right, canvasSize.bottom);
|
||||
final Rect innerClip = Rect.fromLTRB(
|
||||
0.5,
|
||||
canvasSize.bottom / 2 + 0.5,
|
||||
canvasSize.right,
|
||||
canvasSize.bottom,
|
||||
);
|
||||
|
||||
canvas = BitmapCanvas(canvasSize, RenderStrategy());
|
||||
canvas.debugChildOverdraw = true;
|
||||
canvas.clipRect(outerClip, ClipOp.intersect);
|
||||
canvas.drawParagraph(paragraph, const Offset(8.5, 8.5));
|
||||
canvas.clipRect(innerClip, ClipOp.intersect);
|
||||
canvas.drawParagraph(paragraph, Offset(8.5, 8.5 + innerClip.top));
|
||||
|
||||
expect(
|
||||
canvas.rootElement
|
||||
.querySelectorAll('flt-paragraph')
|
||||
.map<String>((DomElement e) => e.innerText)
|
||||
.toList(),
|
||||
<String>['Am I blurry?', 'Am I blurry?'],
|
||||
reason: 'Expected to render text using HTML',
|
||||
);
|
||||
|
||||
appendToScene();
|
||||
|
||||
await matchGoldenFile('bitmap_canvas_draws_high_quality_text.png', region: canvasSize);
|
||||
}, testOn: 'chrome');
|
||||
|
||||
// NOTE: Chrome in --headless mode does not reproduce the bug that this test
|
||||
// attempts to reproduce. However, it's still good to have this test
|
||||
// for potential future regressions related to paint order.
|
||||
test('draws text on top of canvas when transformed and clipped', () async {
|
||||
final ParagraphBuilder builder = ParagraphBuilder(
|
||||
ParagraphStyle(fontFamily: 'Ahem', fontSize: 18),
|
||||
);
|
||||
|
||||
const String text =
|
||||
'This text is intentionally very long to make sure that it '
|
||||
'breaks into multiple lines.';
|
||||
builder.addText(text);
|
||||
|
||||
final CanvasParagraph paragraph = builder.build() as CanvasParagraph;
|
||||
paragraph.layout(const ParagraphConstraints(width: 100));
|
||||
|
||||
final Rect canvasSize = Offset.zero & const Size(500, 500);
|
||||
|
||||
canvas = BitmapCanvas(canvasSize, RenderStrategy());
|
||||
canvas.debugChildOverdraw = true;
|
||||
|
||||
final SurfacePaintData pathPaint =
|
||||
SurfacePaintData()
|
||||
..color = 0xFF7F7F7F
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
const double r = 200.0;
|
||||
const double l = 50.0;
|
||||
|
||||
final Path path = (Path()
|
||||
..moveTo(-l, -l)
|
||||
..lineTo(0, -r)
|
||||
..lineTo(l, -l)
|
||||
..lineTo(r, 0)
|
||||
..lineTo(l, l)
|
||||
..lineTo(0, r)
|
||||
..lineTo(-l, l)
|
||||
..lineTo(-r, 0)
|
||||
..close())
|
||||
.shift(const Offset(250, 250));
|
||||
|
||||
final SurfacePaintData borderPaint =
|
||||
SurfacePaintData()
|
||||
..color = black.value
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
canvas.drawPath(path, pathPaint);
|
||||
canvas.drawParagraph(paragraph, const Offset(180, 50));
|
||||
canvas.drawRect(Rect.fromLTWH(180, 50, paragraph.width, paragraph.height), borderPaint);
|
||||
|
||||
expect(
|
||||
canvas.rootElement
|
||||
.querySelectorAll('flt-paragraph')
|
||||
.map<String?>((DomElement e) => e.text)
|
||||
.toList(),
|
||||
<String>[text],
|
||||
reason: 'Expected to render text using HTML',
|
||||
);
|
||||
|
||||
final SceneBuilder sb = SceneBuilder();
|
||||
sb.pushTransform(
|
||||
Matrix4.diagonal3Values(
|
||||
EngineFlutterDisplay.instance.browserDevicePixelRatio,
|
||||
EngineFlutterDisplay.instance.browserDevicePixelRatio,
|
||||
1.0,
|
||||
).toFloat64(),
|
||||
);
|
||||
sb.pushTransform(Matrix4.rotationZ(math.pi / 2).toFloat64());
|
||||
sb.pushOffset(0, -500);
|
||||
sb.pushClipRect(canvasSize);
|
||||
sb.pop();
|
||||
sb.pop();
|
||||
sb.pop();
|
||||
sb.pop();
|
||||
final SurfaceScene scene = sb.build() as SurfaceScene;
|
||||
final DomElement sceneElement = scene.webOnlyRootElement!;
|
||||
if (isIosSafari) {
|
||||
// Shrink to fit on the iPhone screen.
|
||||
sceneElement.style.position = 'absolute';
|
||||
sceneElement.style.transformOrigin = '0 0 0';
|
||||
sceneElement.style.transform = 'scale(0.3)';
|
||||
}
|
||||
|
||||
sceneElement.querySelector('flt-clip')!.append(canvas.rootElement);
|
||||
sceneHost.append(sceneElement);
|
||||
|
||||
await matchGoldenFile('bitmap_canvas_draws_text_on_top_of_canvas.png', region: canvasSize);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/96498. When
|
||||
// a picture is made of just text that can be rendered using plain HTML,
|
||||
// BitmapCanvas should not create any <canvas> elements as they are expensive.
|
||||
test('does not allocate bitmap canvas just for text', () async {
|
||||
canvas = BitmapCanvas(const Rect.fromLTWH(0, 0, 50, 50), RenderStrategy());
|
||||
|
||||
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(fontFamily: 'Roboto'));
|
||||
builder.addText('Hello');
|
||||
final CanvasParagraph paragraph = builder.build() as CanvasParagraph;
|
||||
paragraph.layout(const ParagraphConstraints(width: 1000));
|
||||
|
||||
canvas.drawParagraph(paragraph, const Offset(8.5, 8.5));
|
||||
expect(canvas.rootElement.querySelectorAll('canvas'), isEmpty);
|
||||
expect(canvas.rootElement.querySelectorAll('flt-paragraph').single.innerText, 'Hello');
|
||||
});
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
// 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 'dart:js_util' as js_util;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart' as engine;
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' hide TextStyle;
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
import 'screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/48683
|
||||
// Should clip image with oval.
|
||||
test('Clips image with oval clip path', () async {
|
||||
final engine.RecordingCanvas rc = engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
final Path path = Path();
|
||||
path.addOval(Rect.fromLTWH(100, 30, testWidth, testHeight));
|
||||
rc.clipPath(path);
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
Rect.fromLTWH(100, 30, testWidth, testHeight),
|
||||
engine.SurfacePaint(),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'image_clipped_by_oval');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/48683
|
||||
test('Clips triangle with oval clip path', () async {
|
||||
final engine.RecordingCanvas rc = engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
rc.save();
|
||||
const double testWidth = 200;
|
||||
const double testHeight = 150;
|
||||
final Path path = Path();
|
||||
path.addOval(const Rect.fromLTWH(100, 30, testWidth, testHeight));
|
||||
rc.clipPath(path);
|
||||
final Path paintPath = Path();
|
||||
paintPath.moveTo(testWidth / 2, 0);
|
||||
paintPath.lineTo(testWidth, testHeight);
|
||||
paintPath.lineTo(0, testHeight);
|
||||
paintPath.close();
|
||||
rc.drawPath(
|
||||
paintPath,
|
||||
engine.SurfacePaint()
|
||||
..color = const Color(0xFF00FF00)
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'triangle_clipped_by_oval');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/78782
|
||||
test('Clips on Safari when clip bounds off screen', () async {
|
||||
final engine.RecordingCanvas rc = engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
rc.save();
|
||||
const double testWidth = 200;
|
||||
const double testHeight = 150;
|
||||
|
||||
final Path paintPath = Path();
|
||||
paintPath.addRect(const Rect.fromLTWH(-50, 0, testWidth, testHeight));
|
||||
paintPath.close();
|
||||
rc.drawPath(
|
||||
paintPath,
|
||||
engine.SurfacePaint()
|
||||
..color = const Color(0xFF000000)
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(-200, 0);
|
||||
path.lineTo(100, 75);
|
||||
path.lineTo(-200, 150);
|
||||
path.close();
|
||||
rc.clipPath(path);
|
||||
rc.drawImageRect(
|
||||
createTestImage(),
|
||||
const Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
const Rect.fromLTWH(-50, 0, testWidth, testHeight),
|
||||
engine.SurfacePaint(),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(
|
||||
rc,
|
||||
'image_clipped_by_triangle_off_screen',
|
||||
region: const Rect.fromLTWH(0, 0, 600, 800),
|
||||
);
|
||||
});
|
||||
|
||||
// Tests oval clipping using border radius 50%.
|
||||
test('Clips against oval', () async {
|
||||
final engine.RecordingCanvas rc = engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
rc.save();
|
||||
const double testWidth = 200;
|
||||
const double testHeight = 150;
|
||||
|
||||
final Path paintPath = Path();
|
||||
paintPath.addRect(const Rect.fromLTWH(-50, 0, testWidth, testHeight));
|
||||
paintPath.close();
|
||||
rc.drawPath(
|
||||
paintPath,
|
||||
engine.SurfacePaint()
|
||||
..color = const Color(0xFF000000)
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
final Path path = Path();
|
||||
path.addOval(const Rect.fromLTRB(-200, 0, 100, 150));
|
||||
rc.clipPath(path);
|
||||
rc.drawImageRect(
|
||||
createTestImage(),
|
||||
const Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
const Rect.fromLTWH(-50, 0, testWidth, testHeight),
|
||||
engine.SurfacePaint(),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(
|
||||
rc,
|
||||
'image_clipped_by_oval_path',
|
||||
region: const Rect.fromLTWH(0, 0, 600, 800),
|
||||
);
|
||||
});
|
||||
|
||||
test('Clips with fillType evenOdd', () async {
|
||||
final engine.RecordingCanvas rc = engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
rc.save();
|
||||
const double testWidth = 400;
|
||||
const double testHeight = 350;
|
||||
|
||||
// draw RGB test image
|
||||
rc.drawImageRect(
|
||||
createTestImage(),
|
||||
const Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
const Rect.fromLTWH(0, 0, testWidth, testHeight),
|
||||
engine.SurfacePaint(),
|
||||
);
|
||||
|
||||
// draw a clipping path with:
|
||||
// 1) an outside larger rectangle
|
||||
// 2) a smaller inner rectangle specified by a path
|
||||
final Path path = Path();
|
||||
path.addRect(const Rect.fromLTWH(0, 0, testWidth, testHeight));
|
||||
const double left = 25;
|
||||
const double top = 30;
|
||||
const double right = 300;
|
||||
const double bottom = 250;
|
||||
path
|
||||
..moveTo(left, top)
|
||||
..lineTo(right, top)
|
||||
..lineTo(right, bottom)
|
||||
..lineTo(left, bottom)
|
||||
..close();
|
||||
path.fillType = PathFillType.evenOdd;
|
||||
rc.clipPath(path);
|
||||
|
||||
// draw an orange paint path of size testWidth and testHeight
|
||||
final Path paintPath = Path();
|
||||
paintPath.addRect(const Rect.fromLTWH(0, 0, testWidth, testHeight));
|
||||
paintPath.close();
|
||||
rc.drawPath(
|
||||
paintPath,
|
||||
engine.SurfacePaint()
|
||||
..color = const Color(0xFFFF9800)
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
rc.restore();
|
||||
|
||||
// when fillType is set to evenOdd from the clipping path, expect the inner
|
||||
// rectangle should clip some of the orange painted portion, revealing the RGB testImage
|
||||
await canvasScreenshot(
|
||||
rc,
|
||||
'clipPath_uses_fillType_evenOdd',
|
||||
region: const Rect.fromLTWH(0, 0, 600, 800),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
engine.HtmlImage createTestImage({int width = 200, int height = 150}) {
|
||||
final engine.DomCanvasElement canvas = engine.createDomCanvasElement(
|
||||
width: width,
|
||||
height: height,
|
||||
);
|
||||
final engine.DomCanvasRenderingContext2D ctx = canvas.context2D;
|
||||
ctx.fillStyle = '#E04040';
|
||||
ctx.fillRect(0, 0, width / 3, height);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#40E080';
|
||||
ctx.fillRect(width / 3, 0, width / 3, height);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#2040E0';
|
||||
ctx.fillRect(2 * width / 3, 0, width / 3, height);
|
||||
ctx.fill();
|
||||
final engine.DomHTMLImageElement imageElement = engine.createDomHTMLImageElement();
|
||||
imageElement.src = js_util.callMethod<String>(canvas, 'toDataURL', <dynamic>[]);
|
||||
return engine.HtmlImage(imageElement, width, height);
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// 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' as engine;
|
||||
import 'package:ui/ui.dart' hide TextStyle;
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
import 'screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
/// Tests context save/restore.
|
||||
Future<void> testMain() async {
|
||||
const double screenWidth = 600.0;
|
||||
const double screenHeight = 800.0;
|
||||
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/49429
|
||||
// Should clip with correct transform.
|
||||
test('Clips image with oval clip path', () async {
|
||||
final engine.RecordingCanvas rc = engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
final engine.SurfacePaint paint =
|
||||
Paint() as engine.SurfacePaint
|
||||
..color = const Color(0xFF00FF00)
|
||||
..style = PaintingStyle.fill;
|
||||
rc.save();
|
||||
final Path ovalPath = Path();
|
||||
ovalPath.addOval(const Rect.fromLTWH(100, 30, 200, 100));
|
||||
rc.clipPath(ovalPath);
|
||||
rc.translate(-500, -500);
|
||||
rc.save();
|
||||
rc.translate(500, 500);
|
||||
rc.drawPath(ovalPath, paint);
|
||||
// The line below was causing SaveClipStack to incorrectly set
|
||||
// transform before path painting.
|
||||
rc.translate(-1000, -1000);
|
||||
rc.save();
|
||||
rc.restore();
|
||||
rc.restore();
|
||||
rc.restore();
|
||||
// The rectangle should paint without clipping since we restored
|
||||
// context.
|
||||
rc.drawRect(const Rect.fromLTWH(0, 0, 4, 200), paint);
|
||||
await canvasScreenshot(rc, 'context_save_restore_transform', canvasRect: screenRect);
|
||||
});
|
||||
|
||||
test('Should restore clip path', () async {
|
||||
final engine.RecordingCanvas rc = engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
final Paint goodPaint =
|
||||
Paint()
|
||||
..color = const Color(0x8000FF00)
|
||||
..style = PaintingStyle.fill;
|
||||
final Paint badPaint =
|
||||
Paint()
|
||||
..color = const Color(0xFFFF0000)
|
||||
..style = PaintingStyle.fill;
|
||||
rc.save();
|
||||
final Path ovalPath = Path();
|
||||
ovalPath.addOval(const Rect.fromLTWH(100, 30, 200, 100));
|
||||
rc.clipPath(ovalPath);
|
||||
rc.translate(-500, -500);
|
||||
rc.save();
|
||||
rc.restore();
|
||||
// The rectangle should be clipped against oval.
|
||||
rc.drawRect(const Rect.fromLTWH(0, 0, 300, 300), badPaint as engine.SurfacePaint);
|
||||
rc.restore();
|
||||
// The rectangle should paint without clipping since we restored
|
||||
// context.
|
||||
rc.drawRect(const Rect.fromLTWH(0, 0, 200, 200), goodPaint as engine.SurfacePaint);
|
||||
await canvasScreenshot(rc, 'context_save_restore_clip', canvasRect: screenRect);
|
||||
});
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
// 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' hide TextStyle;
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const double screenWidth = 600.0;
|
||||
const double screenHeight = 800.0;
|
||||
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
final SurfacePaint testPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFFFF00FF);
|
||||
|
||||
setUpUnitTests();
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/51514
|
||||
test("Canvas is reused and z-index doesn't leak across paints", () async {
|
||||
final EngineCanvas engineCanvas = BitmapCanvas(screenRect, RenderStrategy());
|
||||
const Rect region = Rect.fromLTWH(0, 0, 500, 500);
|
||||
|
||||
// Draw first frame into engine canvas.
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTWH(1, 2, 300, 400));
|
||||
final Path path =
|
||||
Path()
|
||||
..moveTo(3, 0)
|
||||
..lineTo(100, 97);
|
||||
rc.drawPath(path, testPaint);
|
||||
rc.endRecording();
|
||||
rc.apply(engineCanvas, screenRect);
|
||||
engineCanvas.endOfPaint();
|
||||
|
||||
DomElement sceneElement = createDomElement('flt-scene');
|
||||
if (isIosSafari) {
|
||||
// Shrink to fit on the iPhone screen.
|
||||
sceneElement.style.position = 'absolute';
|
||||
sceneElement.style.transformOrigin = '0 0 0';
|
||||
sceneElement.style.transform = 'scale(0.3)';
|
||||
}
|
||||
sceneElement.append(engineCanvas.rootElement);
|
||||
domDocument.body!.append(sceneElement);
|
||||
|
||||
final DomCanvasElement canvas = domDocument.querySelector('canvas')! as DomCanvasElement;
|
||||
// ! Since canvas is first element, it should have zIndex = -1 for correct
|
||||
// paint order.
|
||||
expect(canvas.style.zIndex, '-1');
|
||||
|
||||
// Add id to canvas element to test for reuse.
|
||||
const String kTestId = 'test-id-5698';
|
||||
canvas.id = kTestId;
|
||||
|
||||
sceneElement.remove();
|
||||
// Clear so resources are marked for reuse.
|
||||
|
||||
engineCanvas.clear();
|
||||
|
||||
// Now paint a second scene to same [BitmapCanvas] but paint an image
|
||||
// before the path to move canvas element into second position.
|
||||
final RecordingCanvas rc2 = RecordingCanvas(const Rect.fromLTWH(1, 2, 300, 400));
|
||||
final Path path2 =
|
||||
Path()
|
||||
..moveTo(3, 0)
|
||||
..quadraticBezierTo(100, 0, 100, 100);
|
||||
rc2.drawImage(_createRealTestImage(), Offset.zero, SurfacePaint());
|
||||
rc2.drawPath(path2, testPaint);
|
||||
rc2.endRecording();
|
||||
rc2.apply(engineCanvas, screenRect);
|
||||
|
||||
sceneElement = createDomElement('flt-scene');
|
||||
if (isIosSafari) {
|
||||
// Shrink to fit on the iPhone screen.
|
||||
sceneElement.style.position = 'absolute';
|
||||
sceneElement.style.transformOrigin = '0 0 0';
|
||||
sceneElement.style.transform = 'scale(0.3)';
|
||||
}
|
||||
sceneElement.append(engineCanvas.rootElement);
|
||||
domDocument.body!.append(sceneElement);
|
||||
|
||||
final DomCanvasElement canvas2 = domDocument.querySelector('canvas')! as DomCanvasElement;
|
||||
// ZIndex should have been cleared since we have image element preceding
|
||||
// canvas.
|
||||
expect(canvas.style.zIndex != '-1', isTrue);
|
||||
expect(canvas2.id, kTestId);
|
||||
await matchGoldenFile('bitmap_canvas_reuse_zindex.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
const String _base64Encoded20x20TestImage =
|
||||
'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAC4jAAAuIwF4pT92AAAA'
|
||||
'B3RJTUUH5AMFFBksg4i3gQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAj'
|
||||
'SURBVDjLY2TAC/7jlWVioACMah4ZmhnxpyHG0QAb1UyZZgBjWAIm/clP0AAAAABJRU5ErkJggg==';
|
||||
|
||||
HtmlImage _createRealTestImage() {
|
||||
return HtmlImage(
|
||||
createDomHTMLImageElement()..src = 'data:text/plain;base64,$_base64Encoded20x20TestImage',
|
||||
20,
|
||||
20,
|
||||
);
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// 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/mock_engine_canvas.dart';
|
||||
import '../common/test_initialization.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
await bootstrapAndRunApp(withImplicitView: true);
|
||||
|
||||
group('EngineCanvas', () {
|
||||
late MockEngineCanvas mockCanvas;
|
||||
late ui.Paragraph paragraph;
|
||||
|
||||
void testCanvas(
|
||||
String description,
|
||||
void Function(EngineCanvas canvas) testFn, {
|
||||
ui.Rect canvasSize = const ui.Rect.fromLTWH(0, 0, 100, 100),
|
||||
ui.VoidCallback? whenDone,
|
||||
}) {
|
||||
test(description, () {
|
||||
testFn(BitmapCanvas(canvasSize, RenderStrategy()));
|
||||
testFn(DomCanvas(domDocument.createElement('flt-picture')));
|
||||
testFn(mockCanvas = MockEngineCanvas());
|
||||
whenDone?.call();
|
||||
});
|
||||
}
|
||||
|
||||
testCanvas(
|
||||
'draws laid out paragraph',
|
||||
(EngineCanvas canvas) {
|
||||
const ui.Rect screenRect = ui.Rect.fromLTWH(0, 0, 100, 100);
|
||||
final RecordingCanvas recordingCanvas = RecordingCanvas(screenRect);
|
||||
final ui.ParagraphBuilder builder = ui.ParagraphBuilder(ui.ParagraphStyle());
|
||||
builder.addText('sample');
|
||||
paragraph = builder.build();
|
||||
paragraph.layout(const ui.ParagraphConstraints(width: 100));
|
||||
recordingCanvas.drawParagraph(paragraph, const ui.Offset(10, 10));
|
||||
recordingCanvas.endRecording();
|
||||
canvas.clear();
|
||||
recordingCanvas.apply(canvas, screenRect);
|
||||
},
|
||||
whenDone: () {
|
||||
expect(mockCanvas.methodCallLog, hasLength(3));
|
||||
|
||||
MockCanvasCall call = mockCanvas.methodCallLog[0];
|
||||
expect(call.methodName, 'clear');
|
||||
|
||||
call = mockCanvas.methodCallLog[1];
|
||||
expect(call.methodName, 'drawParagraph');
|
||||
final Map<dynamic, dynamic> arguments = call.arguments as Map<dynamic, dynamic>;
|
||||
expect(arguments['paragraph'], paragraph);
|
||||
expect(arguments['offset'], const ui.Offset(10, 10));
|
||||
},
|
||||
);
|
||||
|
||||
testCanvas(
|
||||
'ignores paragraphs that were not laid out',
|
||||
(EngineCanvas canvas) {
|
||||
const ui.Rect screenRect = ui.Rect.fromLTWH(0, 0, 100, 100);
|
||||
final RecordingCanvas recordingCanvas = RecordingCanvas(screenRect);
|
||||
final ui.ParagraphBuilder builder = ui.ParagraphBuilder(ui.ParagraphStyle());
|
||||
builder.addText('sample');
|
||||
final ui.Paragraph paragraph = builder.build();
|
||||
recordingCanvas.drawParagraph(paragraph, const ui.Offset(10, 10));
|
||||
recordingCanvas.endRecording();
|
||||
canvas.clear();
|
||||
recordingCanvas.apply(canvas, screenRect);
|
||||
},
|
||||
whenDone: () {
|
||||
expect(mockCanvas.methodCallLog, hasLength(2));
|
||||
expect(mockCanvas.methodCallLog[0].methodName, 'clear');
|
||||
expect(mockCanvas.methodCallLog[1].methodName, 'endOfPaint');
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 500, 500);
|
||||
|
||||
late BitmapCanvas canvas;
|
||||
|
||||
setUp(() {
|
||||
canvas = BitmapCanvas(region, RenderStrategy());
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
canvas.rootElement.remove();
|
||||
});
|
||||
|
||||
test('draws paths using nonzero and evenodd winding rules', () async {
|
||||
paintPaths(canvas);
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('canvas_path_winding.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
void paintPaths(BitmapCanvas canvas) {
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(0, 0, 500, 500),
|
||||
SurfacePaintData()
|
||||
..color = 0xFFFFFFFF
|
||||
..style = PaintingStyle.fill,
|
||||
); // white
|
||||
|
||||
final SurfacePaint paintFill =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xFF00B0FF);
|
||||
final SurfacePaint paintStroke =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2
|
||||
..color = const Color(0xFFE00000);
|
||||
final Path path1 =
|
||||
Path()
|
||||
..fillType = PathFillType.evenOdd
|
||||
..moveTo(50, 0)
|
||||
..lineTo(21, 90)
|
||||
..lineTo(98, 35)
|
||||
..lineTo(2, 35)
|
||||
..lineTo(79, 90)
|
||||
..close()
|
||||
..addRect(const Rect.fromLTWH(20, 100, 200, 50))
|
||||
..addRect(const Rect.fromLTWH(40, 120, 160, 10));
|
||||
final Path path2 =
|
||||
Path()
|
||||
..fillType = PathFillType.nonZero
|
||||
..moveTo(50, 200)
|
||||
..lineTo(21, 290)
|
||||
..lineTo(98, 235)
|
||||
..lineTo(2, 235)
|
||||
..lineTo(79, 290)
|
||||
..close()
|
||||
..addRect(const Rect.fromLTWH(20, 300, 200, 50))
|
||||
..addRect(const Rect.fromLTWH(40, 320, 160, 10));
|
||||
canvas.drawPath(path1, paintFill.paintData);
|
||||
canvas.drawPath(path2, paintFill.paintData);
|
||||
canvas.drawPath(path1, paintStroke.paintData);
|
||||
canvas.drawPath(path2, paintStroke.paintData);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
import 'paragraph/helper.dart';
|
||||
import 'screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(
|
||||
withImplicitView: true,
|
||||
emulateTesterEnvironment: false,
|
||||
setUpTestViewDimensions: false,
|
||||
);
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/64734.
|
||||
test('Clips using difference', () async {
|
||||
const Offset shift = Offset(8, 8);
|
||||
const Rect region = Rect.fromLTRB(0, 0, 400, 300);
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
final Rect titleRect = const Rect.fromLTWH(20, 0, 50, 20).shift(shift);
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = const Color(0xff000000)
|
||||
..strokeWidth = 1;
|
||||
canvas.save();
|
||||
try {
|
||||
final Rect borderRect = Rect.fromLTRB(0, 10, region.width, region.height).shift(shift);
|
||||
canvas.clipRect(titleRect, ClipOp.difference);
|
||||
canvas.drawRect(borderRect, paint);
|
||||
} finally {
|
||||
canvas.restore();
|
||||
}
|
||||
canvas.drawRect(titleRect, paint);
|
||||
await canvasScreenshot(
|
||||
canvas,
|
||||
'clip_op_difference',
|
||||
region: const Rect.fromLTRB(0, 0, 420, 360),
|
||||
);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/86345
|
||||
test('Clips with zero width or height', () async {
|
||||
const Rect region = Rect.fromLTRB(0, 0, 400, 300);
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xff00ff00);
|
||||
final SurfacePaint borderPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = const Color(0xffff0000)
|
||||
..strokeWidth = 1;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
final double x = 10 + i * 70;
|
||||
final double y = 10 + j * 70;
|
||||
canvas.save();
|
||||
// Clip.
|
||||
canvas.clipRect(Rect.fromLTWH(x, y, i * 25, j * 25), ClipOp.intersect);
|
||||
// Draw the blue (clipped) rect.
|
||||
canvas.drawRect(Rect.fromLTWH(x, y, 50, 50), paint);
|
||||
final Paragraph p = plain(
|
||||
EngineParagraphStyle(fontFamily: 'Roboto', fontSize: 34),
|
||||
'23',
|
||||
textStyle: EngineTextStyle.only(color: const Color(0xff0000ff)),
|
||||
);
|
||||
p.layout(const ParagraphConstraints(width: double.infinity));
|
||||
canvas.drawParagraph(p, Offset(x, y));
|
||||
canvas.restore();
|
||||
// Draw the red border.
|
||||
canvas.drawRect(Rect.fromLTWH(x, y, 50, 50), borderPaint);
|
||||
}
|
||||
}
|
||||
await canvasScreenshot(canvas, 'clip_zero_width_height', region: region);
|
||||
});
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
// 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' hide BackdropFilterEngineLayer, ClipRectEngineLayer;
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(
|
||||
withImplicitView: true,
|
||||
emulateTesterEnvironment: false,
|
||||
setUpTestViewDimensions: false,
|
||||
);
|
||||
|
||||
setUp(() async {
|
||||
debugShowClipLayers = true;
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
for (final DomNode scene in domDocument.querySelectorAll('flt-scene')) {
|
||||
scene.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// The black circle on the left should not be blurred since it is outside
|
||||
// the clip boundary around backdrop filter. However there should be only
|
||||
// one red dot since the other one should be blurred by filter.
|
||||
test('Background should only blur at ancestor clip boundary', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 190, 130);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture backgroundPicture = _drawBackground(region);
|
||||
builder.addPicture(Offset.zero, backgroundPicture);
|
||||
|
||||
builder.pushClipRect(const Rect.fromLTRB(10, 10, 180, 120));
|
||||
final Picture circles1 = _drawTestPictureWithCircles(region, 30, 30);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
|
||||
builder.pushClipRect(const Rect.fromLTRB(60, 10, 180, 120));
|
||||
builder.pushBackdropFilter(ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0));
|
||||
final Picture circles2 = _drawTestPictureWithCircles(region, 90, 30);
|
||||
builder.addPicture(Offset.zero, circles2);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('backdrop_filter_clip.png', region: region);
|
||||
});
|
||||
|
||||
test('Background should only blur at ancestor clip boundary after move', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 190, 130);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture backgroundPicture = _drawBackground(region);
|
||||
builder.addPicture(Offset.zero, backgroundPicture);
|
||||
final ClipRectEngineLayer clipEngineLayer = builder.pushClipRect(
|
||||
const Rect.fromLTRB(10, 10, 180, 120),
|
||||
);
|
||||
final Picture circles1 = _drawTestPictureWithCircles(region, 30, 30);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
final ClipRectEngineLayer clipEngineLayer2 = builder.pushClipRect(
|
||||
const Rect.fromLTRB(60, 10, 180, 120),
|
||||
);
|
||||
final BackdropFilterEngineLayer oldBackdropFilterLayer = builder.pushBackdropFilter(
|
||||
ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||
);
|
||||
final Picture circles2 = _drawTestPictureWithCircles(region, 90, 30);
|
||||
builder.addPicture(Offset.zero, circles2);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.build();
|
||||
|
||||
// Now reparent filter layer in next scene.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.addPicture(Offset.zero, backgroundPicture);
|
||||
builder2.pushClipRect(const Rect.fromLTRB(10, 10, 180, 120), oldLayer: clipEngineLayer);
|
||||
builder2.addPicture(Offset.zero, circles1);
|
||||
builder2.pushClipRect(const Rect.fromLTRB(10, 75, 180, 120), oldLayer: clipEngineLayer2);
|
||||
builder2.pushBackdropFilter(
|
||||
ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||
oldLayer: oldBackdropFilterLayer,
|
||||
);
|
||||
builder2.addPicture(Offset.zero, circles2);
|
||||
builder2.pop();
|
||||
builder2.pop();
|
||||
builder2.pop();
|
||||
|
||||
domDocument.body!.append(builder2.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('backdrop_filter_clip_moved.png', region: region);
|
||||
});
|
||||
|
||||
// The blur filter should be applied to the background inside the clip even
|
||||
// though there are no children of the backdrop filter.
|
||||
test('Background should blur even if child does not paint', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 190, 130);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture backgroundPicture = _drawBackground(region);
|
||||
builder.addPicture(Offset.zero, backgroundPicture);
|
||||
|
||||
builder.pushClipRect(const Rect.fromLTRB(10, 10, 180, 120));
|
||||
final Picture circles1 = _drawTestPictureWithCircles(region, 30, 30);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
|
||||
builder.pushClipRect(const Rect.fromLTRB(60, 10, 180, 120));
|
||||
builder.pushBackdropFilter(ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0));
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('backdrop_filter_no_child_rendering.png', region: region);
|
||||
});
|
||||
test('colorFilter as imageFilter', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 190, 130);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture backgroundPicture = _drawBackground(region);
|
||||
builder.addPicture(Offset.zero, backgroundPicture);
|
||||
|
||||
builder.pushClipRect(const Rect.fromLTRB(10, 10, 180, 120));
|
||||
final Picture circles1 = _drawTestPictureWithCircles(region, 30, 30);
|
||||
|
||||
// current background color is light green, apply a light yellow colorFilter
|
||||
const ColorFilter colorFilter = ColorFilter.mode(Color(0xFFFFFFB1), BlendMode.modulate);
|
||||
builder.pushBackdropFilter(colorFilter);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('backdrop_filter_colorFilter_as_imageFilter.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
Picture _drawTestPictureWithCircles(Rect region, double offsetX, double offsetY) {
|
||||
final EnginePictureRecorder recorder = PictureRecorder() as EnginePictureRecorder;
|
||||
final RecordingCanvas canvas = recorder.beginRecording(region);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 10, offsetY + 10),
|
||||
10,
|
||||
SurfacePaint()..style = PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 60, offsetY + 10),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(255, 0, 0, 1),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 10, offsetY + 60),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(0, 255, 0, 1),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 60, offsetY + 60),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(0, 0, 255, 1),
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
Picture _drawBackground(Rect region) {
|
||||
final EnginePictureRecorder recorder = PictureRecorder() as EnginePictureRecorder;
|
||||
final RecordingCanvas canvas = recorder.beginRecording(region);
|
||||
canvas.drawRect(
|
||||
region.deflate(8.0),
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xFFE0FFE0),
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// 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 'dart:js_util' as js_util;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' hide TextStyle;
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
test('Blend circles with difference and color', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
rc.drawRect(
|
||||
const Rect.fromLTRB(0, 0, 400, 400),
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromARGB(255, 255, 255, 255),
|
||||
);
|
||||
rc.drawCircle(
|
||||
const Offset(100, 100),
|
||||
80.0,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromARGB(128, 255, 0, 0)
|
||||
..blendMode = BlendMode.difference,
|
||||
);
|
||||
|
||||
rc.drawCircle(
|
||||
const Offset(170, 100),
|
||||
80.0,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..blendMode = BlendMode.color
|
||||
..color = const Color.fromARGB(128, 0, 255, 0),
|
||||
);
|
||||
|
||||
rc.drawCircle(
|
||||
const Offset(135, 170),
|
||||
80.0,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromARGB(128, 255, 0, 0),
|
||||
);
|
||||
rc.restore();
|
||||
|
||||
await canvasScreenshot(rc, 'canvas_blend_circle_diff_color');
|
||||
});
|
||||
|
||||
test('Blend circle and text with multiply', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
rc.drawRect(
|
||||
const Rect.fromLTRB(0, 0, 400, 400),
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromARGB(255, 255, 255, 255),
|
||||
);
|
||||
rc.drawCircle(
|
||||
const Offset(100, 100),
|
||||
80.0,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromARGB(128, 255, 0, 0)
|
||||
..blendMode = BlendMode.difference,
|
||||
);
|
||||
rc.drawCircle(
|
||||
const Offset(170, 100),
|
||||
80.0,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..blendMode = BlendMode.color
|
||||
..color = const Color.fromARGB(128, 0, 255, 0),
|
||||
);
|
||||
|
||||
rc.drawCircle(
|
||||
const Offset(135, 170),
|
||||
80.0,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromARGB(128, 255, 0, 0),
|
||||
);
|
||||
rc.drawImage(
|
||||
createTestImage(),
|
||||
const Offset(135.0, 130.0),
|
||||
SurfacePaint()..blendMode = BlendMode.multiply,
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'canvas_blend_image_multiply');
|
||||
});
|
||||
}
|
||||
|
||||
HtmlImage createTestImage() {
|
||||
const int width = 100;
|
||||
const int height = 50;
|
||||
final DomCanvasElement canvas = createDomCanvasElement(width: width, height: height);
|
||||
final DomCanvasRenderingContext2D ctx = canvas.context2D;
|
||||
ctx.fillStyle = '#E04040';
|
||||
ctx.fillRect(0, 0, 33, 50);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#40E080';
|
||||
ctx.fillRect(33, 0, 33, 50);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#2040E0';
|
||||
ctx.fillRect(66, 0, 33, 50);
|
||||
ctx.fill();
|
||||
final DomHTMLImageElement imageElement = createDomHTMLImageElement();
|
||||
imageElement.src = js_util.callMethod<String>(canvas, 'toDataURL', <dynamic>[]);
|
||||
return HtmlImage(imageElement, width, height);
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
// 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' hide TextStyle;
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
import '../testimage.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
SurfacePaint makePaint() => Paint() as SurfacePaint;
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(withImplicitView: true, setUpTestViewDimensions: false);
|
||||
|
||||
const Color red = Color(0xFFFF0000);
|
||||
const Color green = Color(0xFF00FF00);
|
||||
const Color blue = Color(0xFF2196F3);
|
||||
const Color white = Color(0xFFFFFFFF);
|
||||
const Color grey = Color(0xFF808080);
|
||||
const Color black = Color(0xFF000000);
|
||||
|
||||
final List<List<BlendMode>> modes = <List<BlendMode>>[
|
||||
<BlendMode>[
|
||||
BlendMode.clear,
|
||||
BlendMode.src,
|
||||
BlendMode.dst,
|
||||
BlendMode.srcOver,
|
||||
BlendMode.dstOver,
|
||||
BlendMode.srcIn,
|
||||
BlendMode.dstIn,
|
||||
BlendMode.srcOut,
|
||||
],
|
||||
<BlendMode>[
|
||||
BlendMode.dstOut,
|
||||
BlendMode.srcATop,
|
||||
BlendMode.dstATop,
|
||||
BlendMode.xor,
|
||||
BlendMode.plus,
|
||||
BlendMode.modulate,
|
||||
BlendMode.screen,
|
||||
BlendMode.overlay,
|
||||
],
|
||||
<BlendMode>[
|
||||
BlendMode.darken,
|
||||
BlendMode.lighten,
|
||||
BlendMode.colorDodge,
|
||||
BlendMode.hardLight,
|
||||
BlendMode.softLight,
|
||||
BlendMode.difference,
|
||||
BlendMode.exclusion,
|
||||
BlendMode.multiply,
|
||||
],
|
||||
<BlendMode>[BlendMode.hue, BlendMode.saturation, BlendMode.color, BlendMode.luminosity],
|
||||
];
|
||||
|
||||
for (int blendGroup = 0; blendGroup < 4; ++blendGroup) {
|
||||
test('Draw image with Group$blendGroup blend modes', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 400));
|
||||
rc.save();
|
||||
final List<BlendMode> blendModes = modes[blendGroup];
|
||||
for (int row = 0; row < blendModes.length; row++) {
|
||||
// draw white background for first 4, black for next 4 blends.
|
||||
final double top = row * 50.0;
|
||||
rc.drawRect(Rect.fromLTWH(0, top, 200, 50), makePaint()..color = white);
|
||||
rc.drawRect(Rect.fromLTWH(200, top, 200, 50), makePaint()..color = grey);
|
||||
final BlendMode blendMode = blendModes[row];
|
||||
rc.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
Offset(0, top),
|
||||
makePaint()..colorFilter = EngineColorFilter.mode(red, blendMode),
|
||||
);
|
||||
rc.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
Offset(50, top),
|
||||
makePaint()..colorFilter = EngineColorFilter.mode(green, blendMode),
|
||||
);
|
||||
rc.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
Offset(100, top),
|
||||
makePaint()..colorFilter = EngineColorFilter.mode(blue, blendMode),
|
||||
);
|
||||
rc.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
Offset(150, top),
|
||||
makePaint()..colorFilter = EngineColorFilter.mode(black, blendMode),
|
||||
);
|
||||
rc.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
Offset(200, top),
|
||||
makePaint()..colorFilter = EngineColorFilter.mode(red, blendMode),
|
||||
);
|
||||
rc.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
Offset(250, top),
|
||||
makePaint()..colorFilter = EngineColorFilter.mode(green, blendMode),
|
||||
);
|
||||
rc.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
Offset(300, top),
|
||||
makePaint()..colorFilter = EngineColorFilter.mode(blue, blendMode),
|
||||
);
|
||||
rc.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
Offset(350, top),
|
||||
makePaint()..colorFilter = EngineColorFilter.mode(black, blendMode),
|
||||
);
|
||||
}
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'canvas_image_blend_group$blendGroup');
|
||||
}, skip: isSafari);
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/56971
|
||||
test('Draws image and paragraph at same vertical position', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 400));
|
||||
rc.save();
|
||||
rc.drawRect(const Rect.fromLTWH(0, 50, 200, 50), makePaint()..color = white);
|
||||
rc.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
const Offset(0, 50),
|
||||
makePaint()..colorFilter = const EngineColorFilter.mode(red, BlendMode.srcIn),
|
||||
);
|
||||
|
||||
final Paragraph paragraph = createTestParagraph();
|
||||
const double textLeft = 80.0;
|
||||
const double textTop = 50.0;
|
||||
const double widthConstraint = 300.0;
|
||||
paragraph.layout(const ParagraphConstraints(width: widthConstraint));
|
||||
rc.drawParagraph(paragraph, const Offset(textLeft, textTop));
|
||||
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'canvas_image_blend_and_text');
|
||||
});
|
||||
|
||||
test('Does not re-use styles with same image src', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 400));
|
||||
final HtmlImage flutterImage = createFlutterLogoTestImage();
|
||||
rc.save();
|
||||
rc.drawRect(const Rect.fromLTWH(0, 50, 200, 50), makePaint()..color = white);
|
||||
rc.drawImage(
|
||||
flutterImage,
|
||||
const Offset(0, 50),
|
||||
makePaint()..colorFilter = const EngineColorFilter.mode(red, BlendMode.modulate),
|
||||
);
|
||||
|
||||
// Expect that the colorFilter is only applied to the first image, since the
|
||||
// colorFilter is applied to a clone of the flutterImage and not the original
|
||||
rc.drawImage(flutterImage, const Offset(0, 100), makePaint());
|
||||
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'canvas_image_same_src');
|
||||
});
|
||||
}
|
||||
|
||||
Paragraph createTestParagraph() {
|
||||
final ParagraphBuilder builder = ParagraphBuilder(
|
||||
ParagraphStyle(
|
||||
fontFamily: 'Ahem',
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
);
|
||||
builder.addText('FOO');
|
||||
return builder.build();
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// 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' hide TextStyle;
|
||||
|
||||
import '../screenshot.dart';
|
||||
import '../testimage.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
SurfacePaint makePaint() => Paint() as SurfacePaint;
|
||||
|
||||
Future<void> testMain() async {
|
||||
const double screenWidth = 100.0;
|
||||
const double screenHeight = 100.0;
|
||||
const Rect region = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/76966
|
||||
test('Draws image with dstATop color filter', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
canvas.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
const Offset(10, 10),
|
||||
makePaint()..colorFilter = const EngineColorFilter.mode(Color(0x40000000), BlendMode.dstATop),
|
||||
);
|
||||
await canvasScreenshot(canvas, 'image_color_fiter_dstatop', region: region);
|
||||
});
|
||||
|
||||
test('Draws image with matrix color filter', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
canvas.drawImage(
|
||||
createFlutterLogoTestImage(),
|
||||
const Offset(10, 10),
|
||||
makePaint()
|
||||
..colorFilter = const EngineColorFilter.matrix(<double>[
|
||||
0.2126, 0.7152, 0.0722, 0, 0, //
|
||||
0.2126, 0.7152, 0.0722, 0, 0, //
|
||||
0.2126, 0.7152, 0.0722, 0, 0, //
|
||||
0, 0, 0, 1, 0, //
|
||||
]),
|
||||
);
|
||||
await canvasScreenshot(canvas, 'image_matrix_color_fiter', region: region);
|
||||
});
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
// 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 '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 '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
tearDown(() {
|
||||
ContextStateHandle.debugEmulateWebKitMaskFilter = false;
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/55930
|
||||
void testMaskFilterBlur({bool isWebkit = false}) {
|
||||
final String browser = isWebkit ? 'Safari' : 'Chrome';
|
||||
|
||||
test('renders MaskFilter.blur in $browser', () async {
|
||||
const double screenWidth = 800.0;
|
||||
const double screenHeight = 150.0;
|
||||
const ui.Rect screenRect = ui.Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
|
||||
ContextStateHandle.debugEmulateWebKitMaskFilter = isWebkit;
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.translate(0, 75);
|
||||
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()..maskFilter = const ui.MaskFilter.blur(ui.BlurStyle.normal, 5);
|
||||
|
||||
rc.translate(50, 0);
|
||||
rc.drawRect(ui.Rect.fromCircle(center: ui.Offset.zero, radius: 30), paint);
|
||||
|
||||
rc.translate(100, 0);
|
||||
paint.color = const ui.Color(0xFF00FF00);
|
||||
rc.drawRRect(
|
||||
ui.RRect.fromRectAndRadius(
|
||||
ui.Rect.fromCircle(center: ui.Offset.zero, radius: 30),
|
||||
const ui.Radius.circular(20),
|
||||
),
|
||||
paint,
|
||||
);
|
||||
|
||||
rc.translate(100, 0);
|
||||
paint.color = const ui.Color(0xFF0000FF);
|
||||
rc.drawCircle(ui.Offset.zero, 30, paint);
|
||||
|
||||
rc.translate(100, 0);
|
||||
paint.color = const ui.Color(0xFF00FFFF);
|
||||
rc.drawPath(
|
||||
SurfacePath()
|
||||
..moveTo(-20, 0)
|
||||
..lineTo(0, -50)
|
||||
..lineTo(20, 0)
|
||||
..lineTo(0, 50)
|
||||
..close(),
|
||||
paint,
|
||||
);
|
||||
|
||||
rc.translate(100, 0);
|
||||
paint.color = const ui.Color(0xFFFF00FF);
|
||||
rc.drawOval(ui.Rect.fromCenter(center: ui.Offset.zero, width: 40, height: 100), paint);
|
||||
|
||||
rc.translate(100, 0);
|
||||
paint.color = const ui.Color(0xFF888800);
|
||||
paint.strokeWidth = 5;
|
||||
rc.drawLine(const ui.Offset(-20, -50), const ui.Offset(20, 50), paint);
|
||||
|
||||
rc.translate(100, 0);
|
||||
paint.color = const ui.Color(0xFF888888);
|
||||
rc.drawDRRect(
|
||||
ui.RRect.fromRectAndRadius(
|
||||
ui.Rect.fromCircle(center: ui.Offset.zero, radius: 35),
|
||||
const ui.Radius.circular(20),
|
||||
),
|
||||
ui.RRect.fromRectAndRadius(
|
||||
ui.Rect.fromCircle(center: ui.Offset.zero, radius: 15),
|
||||
const ui.Radius.circular(7),
|
||||
),
|
||||
paint,
|
||||
);
|
||||
|
||||
rc.translate(100, 0);
|
||||
paint.color = const ui.Color(0xFF6500C9);
|
||||
rc.drawRawPoints(
|
||||
ui.PointMode.points,
|
||||
Float32List.fromList(<double>[-10, -10, -10, 10, 10, -10, 10, 10]),
|
||||
paint,
|
||||
);
|
||||
|
||||
await canvasScreenshot(rc, 'mask_filter_$browser', region: screenRect);
|
||||
});
|
||||
|
||||
test('renders transformed MaskFilter.blur in $browser', () async {
|
||||
const double screenWidth = 300.0;
|
||||
const double screenHeight = 300.0;
|
||||
const ui.Rect screenRect = ui.Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
|
||||
ContextStateHandle.debugEmulateWebKitMaskFilter = isWebkit;
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.translate(150, 150);
|
||||
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()..maskFilter = const ui.MaskFilter.blur(ui.BlurStyle.normal, 5);
|
||||
|
||||
const List<ui.Color> colors = <ui.Color>[
|
||||
ui.Color(0xFF000000),
|
||||
ui.Color(0xFF00FF00),
|
||||
ui.Color(0xFF0000FF),
|
||||
ui.Color(0xFF00FFFF),
|
||||
ui.Color(0xFFFF00FF),
|
||||
ui.Color(0xFF888800),
|
||||
ui.Color(0xFF888888),
|
||||
ui.Color(0xFF6500C9),
|
||||
];
|
||||
|
||||
for (final ui.Color color in colors) {
|
||||
paint.color = color;
|
||||
rc.rotate(math.pi / 4);
|
||||
rc.drawRect(ui.Rect.fromCircle(center: const ui.Offset(90, 0), radius: 20), paint);
|
||||
}
|
||||
|
||||
await canvasScreenshot(rc, 'mask_filter_transformed_$browser', region: screenRect);
|
||||
});
|
||||
}
|
||||
|
||||
testMaskFilterBlur();
|
||||
testMaskFilterBlur(isWebkit: true);
|
||||
|
||||
for (final int testDpr in <int>[1, 2, 4]) {
|
||||
test('MaskFilter.blur blurs correctly for device-pixel ratio $testDpr', () async {
|
||||
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(testDpr.toDouble());
|
||||
const ui.Rect screenRect = ui.Rect.fromLTWH(0, 0, 150, 150);
|
||||
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.translate(0, 75);
|
||||
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()..maskFilter = const ui.MaskFilter.blur(ui.BlurStyle.normal, 5);
|
||||
|
||||
rc.translate(75, 0);
|
||||
rc.drawRect(ui.Rect.fromCircle(center: ui.Offset.zero, radius: 30), paint);
|
||||
|
||||
await canvasScreenshot(rc, 'mask_filter_blur_dpr_$testDpr', region: screenRect);
|
||||
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(1.0);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
|
||||
const Rect region = Rect.fromLTWH(0, 0, 500, 500);
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(
|
||||
withImplicitView: true,
|
||||
emulateTesterEnvironment: false,
|
||||
setUpTestViewDimensions: false,
|
||||
);
|
||||
|
||||
setUp(() async {
|
||||
debugShowClipLayers = true;
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
for (final DomNode scene in domDocument.querySelectorAll('flt-scene')) {
|
||||
scene.remove();
|
||||
}
|
||||
});
|
||||
|
||||
test('Should apply color filter to image', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture backgroundPicture = _drawBackground();
|
||||
builder.addPicture(Offset.zero, backgroundPicture);
|
||||
builder.pushColorFilter(const EngineColorFilter.mode(Color(0xF0000080), BlendMode.color));
|
||||
final Picture circles1 = _drawTestPictureWithCircles(30, 30);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
builder.pop();
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
// TODO(ferhat): update golden for this test after canvas sandwich detection is
|
||||
// added to RecordingCanvas.
|
||||
await matchGoldenFile('color_filter_blendMode_color.png', region: region);
|
||||
});
|
||||
|
||||
test('Should apply matrix color filter to image', () async {
|
||||
final List<double> colorMatrix = <double>[
|
||||
0.2126, 0.7152, 0.0722, 0, 0, //
|
||||
0.2126, 0.7152, 0.0722, 0, 0, //
|
||||
0.2126, 0.7152, 0.0722, 0, 0, //
|
||||
0, 0, 0, 1, 0, //
|
||||
];
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture backgroundPicture = _drawBackground();
|
||||
builder.addPicture(Offset.zero, backgroundPicture);
|
||||
builder.pushColorFilter(EngineColorFilter.matrix(colorMatrix));
|
||||
final Picture circles1 = _drawTestPictureWithCircles(30, 30);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
builder.pop();
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
await matchGoldenFile('color_filter_matrix.png', region: region);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/85733
|
||||
test('Should apply mode color filter to circles', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture backgroundPicture = _drawBackground();
|
||||
builder.addPicture(Offset.zero, backgroundPicture);
|
||||
builder.pushColorFilter(const ColorFilter.mode(Color(0xFFFF0000), BlendMode.srcIn));
|
||||
final Picture circles1 = _drawTestPictureWithCircles(30, 30);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
builder.pop();
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
await matchGoldenFile('color_filter_mode.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
Picture _drawTestPictureWithCircles(double offsetX, double offsetY) {
|
||||
final EnginePictureRecorder recorder = PictureRecorder() as EnginePictureRecorder;
|
||||
final RecordingCanvas canvas = recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 10, offsetY + 10),
|
||||
10,
|
||||
(Paint()..style = PaintingStyle.fill) as SurfacePaint,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 60, offsetY + 10),
|
||||
10,
|
||||
(Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(255, 0, 0, 1))
|
||||
as SurfacePaint,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 10, offsetY + 60),
|
||||
10,
|
||||
(Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(0, 255, 0, 1))
|
||||
as SurfacePaint,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 60, offsetY + 60),
|
||||
10,
|
||||
(Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(0, 0, 255, 1))
|
||||
as SurfacePaint,
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
Picture _drawBackground() {
|
||||
final EnginePictureRecorder recorder = PictureRecorder() as EnginePictureRecorder;
|
||||
final RecordingCanvas canvas = recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(8, 8, 400.0 - 16, 400.0 - 16),
|
||||
(Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xFFE0FFE0))
|
||||
as SurfacePaint,
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
@@ -1,683 +0,0 @@
|
||||
// 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 'dart:math' as math;
|
||||
|
||||
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/matchers.dart';
|
||||
import '../../common/test_initialization.dart';
|
||||
|
||||
const ui.Rect region = ui.Rect.fromLTWH(0, 0, 500, 100);
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(
|
||||
withImplicitView: true,
|
||||
emulateTesterEnvironment: false,
|
||||
setUpTestViewDimensions: false,
|
||||
);
|
||||
|
||||
setUp(() async {
|
||||
// To debug test failures uncomment the following to visualize clipping
|
||||
// layers:
|
||||
// debugShowClipLayers = true;
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
for (final DomNode scene in domDocument.querySelectorAll('flt-scene')) {
|
||||
scene.remove();
|
||||
}
|
||||
});
|
||||
|
||||
test('pushClipRect', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(10, 10, 60, 60));
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_shifted_clip_rect.png', region: region);
|
||||
});
|
||||
|
||||
test('pushClipRect with offset and transform', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
|
||||
builder.pushOffset(0, 60);
|
||||
builder.pushTransform(Matrix4.diagonal3Values(1, -1, 1).toFloat64());
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(10, 10, 60, 60));
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_clip_rect_with_offset_and_transform.png', region: region);
|
||||
});
|
||||
|
||||
test('pushClipRect with offset and transform ClipOp none should not clip', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
|
||||
builder.pushOffset(0, 80);
|
||||
builder.pushTransform(Matrix4.diagonal3Values(1, -1, 1).toFloat64());
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(10, 10, 60, 60), clipBehavior: ui.Clip.none);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_clip_rect_clipop_none.png', region: region);
|
||||
});
|
||||
|
||||
test('pushClipRRect with offset and transform ClipOp none should not clip', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
|
||||
builder.pushOffset(0, 80);
|
||||
builder.pushTransform(Matrix4.diagonal3Values(1, -1, 1).toFloat64());
|
||||
builder.pushClipRRect(
|
||||
ui.RRect.fromRectAndRadius(
|
||||
const ui.Rect.fromLTRB(10, 10, 60, 60),
|
||||
const ui.Radius.circular(1),
|
||||
),
|
||||
clipBehavior: ui.Clip.none,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_clip_rrect_clipop_none.png', region: region);
|
||||
});
|
||||
|
||||
test('pushClipRRect', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushClipRRect(ui.RRect.fromLTRBR(10, 10, 60, 60, const ui.Radius.circular(5)));
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_shifted_clip_rrect.png', region: region);
|
||||
});
|
||||
|
||||
test('pushImageFilter blur', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushImageFilter(ui.ImageFilter.blur(sigmaX: 1, sigmaY: 3));
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_image_filter.png', region: region);
|
||||
});
|
||||
|
||||
test('pushImageFilter matrix', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushImageFilter(
|
||||
ui.ImageFilter.matrix(
|
||||
(Matrix4.identity()
|
||||
..translate(40, 10)
|
||||
..rotateZ(math.pi / 6)
|
||||
..scale(0.75, 0.75))
|
||||
.toFloat64(),
|
||||
),
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_image_filter_matrix.png', region: region);
|
||||
});
|
||||
|
||||
test('pushImageFilter using mode ColorFilter', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
// Applying the colorFilter should turn all the circles red.
|
||||
builder.pushImageFilter(const ui.ColorFilter.mode(ui.Color(0xFFFF0000), ui.BlendMode.srcIn));
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_image_filter_using_mode_color_filter.png', region: region);
|
||||
});
|
||||
|
||||
test('pushImageFilter using matrix ColorFilter', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
// Apply a "greyscale" color filter.
|
||||
final List<double> colorMatrix = <double>[
|
||||
0.2126, 0.7152, 0.0722, 0, 0, //
|
||||
0.2126, 0.7152, 0.0722, 0, 0, //
|
||||
0.2126, 0.7152, 0.0722, 0, 0, //
|
||||
0, 0, 0, 1, 0, //
|
||||
];
|
||||
|
||||
builder.pushImageFilter(ui.ColorFilter.matrix(colorMatrix));
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_image_filter_using_matrix_color_filter.png', region: region);
|
||||
});
|
||||
|
||||
group('Cull rect computation', () {
|
||||
_testCullRectComputation();
|
||||
});
|
||||
}
|
||||
|
||||
void _testCullRectComputation() {
|
||||
// Draw a picture larger that screen. Verify that cull rect is equal to screen
|
||||
// bounds.
|
||||
test('fills screen bounds', () async {
|
||||
final ui.SceneBuilder builder = ui.SceneBuilder();
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(ui.Offset.zero, 10000, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
});
|
||||
builder.build();
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, const ui.Rect.fromLTRB(0, 0, 500, 100));
|
||||
// Needs ability to set iframe to 500,100 size. Current screen seems to be 500,500
|
||||
// https://github.com/flutter/flutter/issues/40395
|
||||
}, skip: true);
|
||||
|
||||
// Draw a picture that overflows the screen. Verify that cull rect is the
|
||||
// intersection of screen bounds and paint bounds.
|
||||
test('intersects with screen bounds', () async {
|
||||
final ui.SceneBuilder builder = ui.SceneBuilder();
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(ui.Offset.zero, 20, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
});
|
||||
builder.build();
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, const ui.Rect.fromLTRB(0, 0, 20, 20));
|
||||
});
|
||||
|
||||
// Draw a picture that's fully outside the screen bounds. Verify the cull rect
|
||||
// is zero.
|
||||
test('fully outside screen bounds', () async {
|
||||
final ui.SceneBuilder builder = ui.SceneBuilder();
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(-100, -100),
|
||||
20,
|
||||
SurfacePaint()..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
});
|
||||
builder.build();
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, ui.Rect.zero);
|
||||
expect(picture.debugExactGlobalCullRect, ui.Rect.zero);
|
||||
});
|
||||
|
||||
// Draw a picture that's fully inside the screen. Verify that cull rect is
|
||||
// equal to the paint bounds.
|
||||
test('limits to paint bounds if no clip layers', () async {
|
||||
final ui.SceneBuilder builder = ui.SceneBuilder();
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(const ui.Offset(50, 50), 10, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
});
|
||||
builder.build();
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, const ui.Rect.fromLTRB(40, 40, 60, 60));
|
||||
});
|
||||
|
||||
// Draw a picture smaller than the screen. Offset it such that it remains
|
||||
// fully inside the screen bounds. Verify that cull rect is still just the
|
||||
// paint bounds.
|
||||
test('offset does not affect paint bounds', () async {
|
||||
final ui.SceneBuilder builder = ui.SceneBuilder();
|
||||
|
||||
builder.pushOffset(10, 10);
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(const ui.Offset(50, 50), 10, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
});
|
||||
builder.pop();
|
||||
|
||||
builder.build();
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, const ui.Rect.fromLTRB(40, 40, 60, 60));
|
||||
});
|
||||
|
||||
// Draw a picture smaller than the screen. Offset it such that the picture
|
||||
// overflows screen bounds. Verify that the cull rect is the intersection
|
||||
// between screen bounds and paint bounds.
|
||||
test('offset overflows paint bounds', () async {
|
||||
final ui.SceneBuilder builder = ui.SceneBuilder();
|
||||
|
||||
builder.pushOffset(0, 90);
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(ui.Offset.zero, 20, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
});
|
||||
builder.pop();
|
||||
|
||||
builder.build();
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.debugExactGlobalCullRect, const ui.Rect.fromLTRB(0, 70, 20, 100));
|
||||
expect(picture.optimalLocalCullRect, const ui.Rect.fromLTRB(0, -20, 20, 10));
|
||||
// Needs ability to set iframe to 500,100 size. Current screen seems to be 500,500
|
||||
// https://github.com/flutter/flutter/issues/40395
|
||||
}, skip: true);
|
||||
|
||||
// Draw a picture inside a layer clip but fill all available space inside it.
|
||||
// Verify that the cull rect is equal to the layer clip.
|
||||
test('fills layer clip rect', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushClipRect(const ui.Rect.fromLTWH(10, 10, 60, 60));
|
||||
|
||||
builder.pushClipRect(const ui.Rect.fromLTWH(40, 40, 60, 60));
|
||||
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(ui.Offset.zero, 10000, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
});
|
||||
|
||||
builder.pop(); // pushClipRect
|
||||
builder.pop(); // pushClipRect
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_cull_rect_fills_layer_clip.png', region: region);
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, const ui.Rect.fromLTRB(40, 40, 70, 70));
|
||||
});
|
||||
|
||||
// Draw a picture inside a layer clip but position the picture such that its
|
||||
// paint bounds overflow the layer clip. Verify that the cull rect is the
|
||||
// intersection between the layer clip and paint bounds.
|
||||
test('intersects layer clip rect and paint bounds', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushClipRect(const ui.Rect.fromLTWH(10, 10, 60, 60));
|
||||
|
||||
builder.pushClipRect(const ui.Rect.fromLTWH(40, 40, 60, 60));
|
||||
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(const ui.Offset(80, 55), 30, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
});
|
||||
|
||||
builder.pop(); // pushClipRect
|
||||
builder.pop(); // pushClipRect
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile(
|
||||
'compositing_cull_rect_intersects_clip_and_paint_bounds.png',
|
||||
region: region,
|
||||
);
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, const ui.Rect.fromLTRB(50, 40, 70, 70));
|
||||
});
|
||||
|
||||
// Draw a picture inside a layer clip that's positioned inside the clip using
|
||||
// an offset layer. Verify that the cull rect is the intersection between the
|
||||
// layer clip and the offset paint bounds.
|
||||
test('offsets picture inside layer clip rect', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushClipRect(const ui.Rect.fromLTWH(10, 10, 60, 60));
|
||||
|
||||
builder.pushClipRect(const ui.Rect.fromLTWH(40, 40, 60, 60));
|
||||
|
||||
builder.pushOffset(55, 70);
|
||||
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(ui.Offset.zero, 20, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
});
|
||||
|
||||
builder.pop(); // pushOffset
|
||||
builder.pop(); // pushClipRect
|
||||
builder.pop(); // pushClipRect
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_cull_rect_offset_inside_layer_clip.png', region: region);
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, const ui.Rect.fromLTRB(-15.0, -20.0, 15.0, 0.0));
|
||||
});
|
||||
|
||||
// Draw a picture inside a layer clip that's positioned an offset layer such
|
||||
// that the picture is push completely outside the clip area. Verify that the
|
||||
// cull rect is zero.
|
||||
test('zero intersection with clip', () async {
|
||||
final ui.SceneBuilder builder = ui.SceneBuilder();
|
||||
builder.pushClipRect(const ui.Rect.fromLTWH(10, 10, 60, 60));
|
||||
|
||||
builder.pushClipRect(const ui.Rect.fromLTWH(40, 40, 60, 60));
|
||||
|
||||
builder.pushOffset(100, 50);
|
||||
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawCircle(ui.Offset.zero, 20, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
});
|
||||
|
||||
builder.pop(); // pushOffset
|
||||
builder.pop(); // pushClipRect
|
||||
builder.pop(); // pushClipRect
|
||||
|
||||
builder.build();
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, ui.Rect.zero);
|
||||
expect(picture.debugExactGlobalCullRect, ui.Rect.zero);
|
||||
});
|
||||
|
||||
// Draw a picture inside a rotated clip. Verify that the cull rect is big
|
||||
// enough to fit the rotated clip.
|
||||
test('rotates clip and the picture', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushOffset(80, 50);
|
||||
|
||||
builder.pushTransform(Matrix4.rotationZ(-math.pi / 4).toFloat64());
|
||||
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(-10, -10, 10, 10));
|
||||
|
||||
builder.pushTransform(Matrix4.rotationZ(math.pi / 4).toFloat64());
|
||||
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawPaint(
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromRGBO(0, 0, 255, 0.6)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(-5, -5, 5, 5),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromRGBO(0, 255, 0, 1.0)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
});
|
||||
|
||||
builder.pop(); // pushTransform
|
||||
builder.pop(); // pushClipRect
|
||||
builder.pop(); // pushTransform
|
||||
builder.pop(); // pushOffset
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_cull_rect_rotated.png', region: region);
|
||||
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
expect(
|
||||
picture.optimalLocalCullRect,
|
||||
within(distance: 0.05, from: const ui.Rect.fromLTRB(-14.1, -14.1, 14.1, 14.1)),
|
||||
);
|
||||
});
|
||||
|
||||
test('pushClipPath', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final ui.Path path = ui.Path();
|
||||
path.addRect(const ui.Rect.fromLTRB(10, 10, 60, 60));
|
||||
builder.pushClipPath(path);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_clip_path.png', region: region);
|
||||
});
|
||||
|
||||
// Draw a picture inside a rotated clip. Verify that the cull rect is big
|
||||
// enough to fit the rotated clip.
|
||||
test('clips correctly when using 3d transforms', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
|
||||
builder.pushTransform(
|
||||
Matrix4.diagonal3Values(
|
||||
EngineFlutterDisplay.instance.browserDevicePixelRatio,
|
||||
EngineFlutterDisplay.instance.browserDevicePixelRatio,
|
||||
1.0,
|
||||
).toFloat64(),
|
||||
);
|
||||
|
||||
// TODO(yjbanov): see the TODO below.
|
||||
// final double screenWidth = domWindow.innerWidth.toDouble();
|
||||
// final double screenHeight = domWindow.innerHeight.toDouble();
|
||||
|
||||
final Matrix4 scaleTransform = Matrix4.identity().scaled(0.5, 0.2);
|
||||
builder.pushTransform(scaleTransform.toFloat64());
|
||||
|
||||
builder.pushOffset(400, 200);
|
||||
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(-200, -200, 200, 200));
|
||||
|
||||
builder.pushTransform(Matrix4.rotationY(45.0 * math.pi / 180.0).toFloat64());
|
||||
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(-140, -140, 140, 140));
|
||||
|
||||
builder.pushTransform(Matrix4.translationValues(0, 0, -50).toFloat64());
|
||||
|
||||
drawWithBitmapCanvas(builder, (RecordingCanvas canvas) {
|
||||
canvas.drawPaint(
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromRGBO(0, 0, 255, 0.6)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
// ui.Rect will be clipped.
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(-150, -150, 150, 150),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromRGBO(0, 255, 0, 1.0)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
// Should be outside the clip range.
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(-150, -150, -140, -140),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromARGB(0xE0, 255, 0, 0)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(140, -150, 150, -140),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromARGB(0xE0, 255, 0, 0)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(-150, 140, -140, 150),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromARGB(0xE0, 255, 0, 0)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(140, 140, 150, 150),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromARGB(0xE0, 255, 0, 0)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
// Should be inside clip range
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(-100, -100, -90, -90),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromARGB(0xE0, 0, 0, 0x80)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(90, -100, 100, -90),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromARGB(0xE0, 0, 0, 0x80)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(-100, 90, -90, 100),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromARGB(0xE0, 0, 0, 0x80)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawRect(
|
||||
const ui.Rect.fromLTRB(90, 90, 100, 100),
|
||||
SurfacePaint()
|
||||
..color = const ui.Color.fromARGB(0xE0, 0, 0, 0x80)
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
});
|
||||
|
||||
builder.pop(); // pushTransform Z-50
|
||||
builder.pop(); // pushClipRect
|
||||
builder.pop(); // pushTransform 3D rotate
|
||||
builder.pop(); // pushClipRect
|
||||
builder.pop(); // pushOffset
|
||||
builder.pop(); // pushTransform scale
|
||||
builder.pop(); // pushTransform scale devicepixelratio
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_3d_rotate1.png', region: region);
|
||||
|
||||
// ignore: unused_local_variable
|
||||
final PersistedPicture picture = enumeratePictures().single;
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/40395)
|
||||
// Needs ability to set iframe to 500,100 size. Current screen seems to be 500,500.
|
||||
// expect(
|
||||
// picture.optimalLocalCullRect,
|
||||
// within(
|
||||
// distance: 0.05,
|
||||
// from: ui.Rect.fromLTRB(
|
||||
// -140, -140, screenWidth - 360.0, screenHeight + 40.0)),
|
||||
// );
|
||||
});
|
||||
|
||||
// This test reproduces text blurriness when two pieces of text appear inside
|
||||
// two nested clips:
|
||||
//
|
||||
// ┌───────────────────────┐
|
||||
// │ text in outer clip │
|
||||
// │ ┌────────────────────┐│
|
||||
// │ │ text in inner clip ││
|
||||
// │ └────────────────────┘│
|
||||
// └───────────────────────┘
|
||||
//
|
||||
// This test clips using layers. See a similar test in `bitmap_canvas_golden_test.dart`,
|
||||
// which clips using canvas.
|
||||
//
|
||||
// More details: https://github.com/flutter/flutter/issues/32274
|
||||
test('renders clipped text with high quality', () async {
|
||||
// To reproduce blurriness we need real clipping.
|
||||
final CanvasParagraph paragraph =
|
||||
(ui.ParagraphBuilder(ui.ParagraphStyle(fontFamily: 'Roboto'))
|
||||
// Use a decoration to force rendering in DOM mode.
|
||||
..pushStyle(
|
||||
ui.TextStyle(
|
||||
decoration: ui.TextDecoration.lineThrough,
|
||||
decorationColor: const ui.Color(0x00000000),
|
||||
),
|
||||
)
|
||||
..addText('Am I blurry?'))
|
||||
.build()
|
||||
as CanvasParagraph;
|
||||
paragraph.layout(const ui.ParagraphConstraints(width: 1000));
|
||||
|
||||
final ui.Rect canvasSize = ui.Rect.fromLTRB(
|
||||
0,
|
||||
0,
|
||||
paragraph.maxIntrinsicWidth + 16,
|
||||
2 * paragraph.height + 32,
|
||||
);
|
||||
final ui.Rect outerClip = ui.Rect.fromLTRB(0.5, 0.5, canvasSize.right, canvasSize.bottom);
|
||||
final ui.Rect innerClip = ui.Rect.fromLTRB(
|
||||
0.5,
|
||||
canvasSize.bottom / 2 + 0.5,
|
||||
canvasSize.right,
|
||||
canvasSize.bottom,
|
||||
);
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
|
||||
builder.pushClipRect(outerClip);
|
||||
|
||||
{
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(outerClip);
|
||||
canvas.drawParagraph(paragraph, const ui.Offset(8.5, 8.5));
|
||||
final ui.Picture picture = recorder.endRecording();
|
||||
expect(paragraph.canDrawOnCanvas, isFalse);
|
||||
|
||||
builder.addPicture(ui.Offset.zero, picture);
|
||||
}
|
||||
|
||||
builder.pushClipRect(innerClip);
|
||||
{
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(innerClip);
|
||||
canvas.drawParagraph(paragraph, ui.Offset(8.5, 8.5 + innerClip.top));
|
||||
final ui.Picture picture = recorder.endRecording();
|
||||
expect(paragraph.canDrawOnCanvas, isFalse);
|
||||
|
||||
builder.addPicture(ui.Offset.zero, picture);
|
||||
}
|
||||
builder.pop(); // inner clip
|
||||
builder.pop(); // outer clip
|
||||
|
||||
final DomElement sceneElement = builder.build().webOnlyRootElement!;
|
||||
expect(
|
||||
sceneElement
|
||||
.querySelectorAll('flt-paragraph')
|
||||
.map<String>((DomElement e) => e.innerText)
|
||||
.toList(),
|
||||
<String>['Am I blurry?', 'Am I blurry?'],
|
||||
reason: 'Expected to render text using HTML',
|
||||
);
|
||||
domDocument.body!.append(sceneElement);
|
||||
|
||||
await matchGoldenFile('compositing_draw_high_quality_text.png', region: canvasSize);
|
||||
}, testOn: 'chrome');
|
||||
}
|
||||
|
||||
void _drawTestPicture(ui.SceneBuilder builder) {
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(const ui.Rect.fromLTRB(0, 0, 100, 100));
|
||||
canvas.drawCircle(const ui.Offset(10, 10), 10, SurfacePaint()..style = ui.PaintingStyle.fill);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(60, 10),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = const ui.Color.fromRGBO(255, 0, 0, 1),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(10, 60),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = const ui.Color.fromRGBO(0, 255, 0, 1),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(60, 60),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = const ui.Color.fromRGBO(0, 0, 255, 1),
|
||||
);
|
||||
final ui.Picture picture = recorder.endRecording();
|
||||
|
||||
builder.addPicture(ui.Offset.zero, picture);
|
||||
}
|
||||
|
||||
typedef PaintCallback = void Function(RecordingCanvas canvas);
|
||||
|
||||
void drawWithBitmapCanvas(
|
||||
ui.SceneBuilder builder,
|
||||
PaintCallback callback, {
|
||||
ui.Rect bounds = ui.Rect.largest,
|
||||
}) {
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(bounds);
|
||||
|
||||
canvas.debugEnforceArbitraryPaint();
|
||||
callback(canvas);
|
||||
final ui.Picture picture = recorder.endRecording();
|
||||
|
||||
builder.addPicture(ui.Offset.zero, picture);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// 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' hide TextStyle;
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
test('Should blur rectangles based on sigma.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
for (int blurSigma = 1; blurSigma < 10; blurSigma += 2) {
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..color = const Color(0xFF2fdfd2)
|
||||
..maskFilter = MaskFilter.blur(BlurStyle.normal, blurSigma.toDouble());
|
||||
rc.drawRect(Rect.fromLTWH(15.0, 15.0 + blurSigma * 40, 200, 20), paint);
|
||||
}
|
||||
await canvasScreenshot(rc, 'dom_mask_filter_blur');
|
||||
});
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// 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';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
final List<String> warnings = <String>[];
|
||||
late void Function(String) oldPrintWarning;
|
||||
|
||||
setUpAll(() async {
|
||||
oldPrintWarning = printWarning;
|
||||
printWarning = (String warning) {
|
||||
warnings.add(warning);
|
||||
};
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
printWarning = oldPrintWarning;
|
||||
});
|
||||
|
||||
test('Emit a warning when the HTML Renderer was picked.', () {
|
||||
final Renderer chosenRenderer = renderer;
|
||||
|
||||
expect(chosenRenderer, isA<HtmlRenderer>());
|
||||
expect(
|
||||
warnings,
|
||||
contains(contains('See: https://docs.flutter.dev/to/web-html-renderer-deprecation')),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// 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';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
group('$adjustRectForDom', () {
|
||||
test('does not change rect when not necessary', () async {
|
||||
const Rect rect = Rect.fromLTWH(10, 20, 140, 160);
|
||||
expect(adjustRectForDom(rect, SurfacePaintData()..style = PaintingStyle.fill), rect);
|
||||
expect(
|
||||
adjustRectForDom(
|
||||
rect,
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 0,
|
||||
),
|
||||
rect,
|
||||
);
|
||||
});
|
||||
|
||||
test('takes stroke width into consideration', () async {
|
||||
const Rect rect = Rect.fromLTWH(10, 20, 140, 160);
|
||||
expect(
|
||||
adjustRectForDom(
|
||||
rect,
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1,
|
||||
),
|
||||
const Rect.fromLTWH(9.5, 19.5, 139, 159),
|
||||
);
|
||||
expect(
|
||||
adjustRectForDom(
|
||||
rect,
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 10,
|
||||
),
|
||||
const Rect.fromLTWH(5, 15, 130, 150),
|
||||
);
|
||||
expect(
|
||||
adjustRectForDom(
|
||||
rect,
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 15,
|
||||
),
|
||||
const Rect.fromLTWH(2.5, 12.5, 125, 145),
|
||||
);
|
||||
});
|
||||
|
||||
test('flips rect when necessary', () {
|
||||
Rect rect = const Rect.fromLTWH(100, 200, -40, -60);
|
||||
expect(
|
||||
adjustRectForDom(rect, SurfacePaintData()..style = PaintingStyle.fill),
|
||||
const Rect.fromLTWH(60, 140, 40, 60),
|
||||
);
|
||||
|
||||
rect = const Rect.fromLTWH(100, 200, 40, -60);
|
||||
expect(
|
||||
adjustRectForDom(rect, SurfacePaintData()..style = PaintingStyle.fill),
|
||||
const Rect.fromLTWH(100, 140, 40, 60),
|
||||
);
|
||||
|
||||
rect = const Rect.fromLTWH(100, 200, -40, 60);
|
||||
expect(
|
||||
adjustRectForDom(rect, SurfacePaintData()..style = PaintingStyle.fill),
|
||||
const Rect.fromLTWH(60, 200, 40, 60),
|
||||
);
|
||||
});
|
||||
|
||||
test('handles stroke width greater than width or height', () {
|
||||
const Rect rect = Rect.fromLTWH(100, 200, 20, 70);
|
||||
expect(
|
||||
adjustRectForDom(
|
||||
rect,
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 50,
|
||||
),
|
||||
const Rect.fromLTWH(75, 175, 0, 20),
|
||||
);
|
||||
expect(
|
||||
adjustRectForDom(
|
||||
rect,
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 80,
|
||||
),
|
||||
const Rect.fromLTWH(60, 160, 0, 0),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// 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 'dart:math' as math;
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 400, 600);
|
||||
|
||||
late BitmapCanvas canvas;
|
||||
|
||||
setUp(() {
|
||||
canvas = BitmapCanvas(region, RenderStrategy());
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
canvas.rootElement.remove();
|
||||
});
|
||||
|
||||
test('draws arcs with largeArc , anticlockwise variations', () async {
|
||||
paintArc(canvas, Offset.zero, distance: 20);
|
||||
paintArc(canvas, const Offset(200, 0), largeArc: true, distance: 20);
|
||||
paintArc(canvas, const Offset(0, 150), clockwise: true, distance: 20);
|
||||
paintArc(canvas, const Offset(200, 150), largeArc: true, clockwise: true, distance: 20);
|
||||
paintArc(canvas, const Offset(0, 300), distance: -20);
|
||||
paintArc(canvas, const Offset(200, 300), largeArc: true, distance: -20);
|
||||
paintArc(canvas, const Offset(0, 400), clockwise: true, distance: -20);
|
||||
paintArc(canvas, const Offset(200, 400), largeArc: true, clockwise: true, distance: -20);
|
||||
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('canvas_arc_to_point.png', region: region);
|
||||
});
|
||||
|
||||
test('Path.addArc that starts new path has correct start point', () async {
|
||||
const Rect rect = Rect.fromLTWH(20, 20, 200, 200);
|
||||
final Path p =
|
||||
Path()
|
||||
..fillType = PathFillType.evenOdd
|
||||
..addRect(rect)
|
||||
..addArc(
|
||||
Rect.fromCircle(center: rect.center, radius: rect.size.shortestSide / 2),
|
||||
0.25 * math.pi,
|
||||
1.5 * math.pi,
|
||||
);
|
||||
canvas.drawPath(
|
||||
p,
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFFFF9800 // orange
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('canvas_addarc.png', region: region);
|
||||
});
|
||||
|
||||
test('Should render counter clockwise arcs', () async {
|
||||
final Path path = Path();
|
||||
path.moveTo(149.999999999999997, 50);
|
||||
path.lineTo(149.999999999999997, 20);
|
||||
path.arcTo(
|
||||
const Rect.fromLTRB(20, 20, 280, 280),
|
||||
4.71238898038469,
|
||||
5.759586531581287 - 4.71238898038469,
|
||||
true,
|
||||
);
|
||||
path.lineTo(236.60254037844385, 99.99999999999999);
|
||||
path.arcTo(
|
||||
const Rect.fromLTRB(50, 50, 250, 250),
|
||||
5.759586531581287,
|
||||
4.71238898038469 - 5.759586531581287,
|
||||
true,
|
||||
);
|
||||
path.lineTo(149.999999999999997, 20);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFFFF9800 // orange
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('canvas_addarc_ccw.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
void paintArc(
|
||||
BitmapCanvas canvas,
|
||||
Offset offset, {
|
||||
bool largeArc = false,
|
||||
bool clockwise = false,
|
||||
double distance = 0,
|
||||
}) {
|
||||
final Offset startP = Offset(75 - distance + offset.dx, 75 - distance + offset.dy);
|
||||
final Offset endP = Offset(75.0 + distance + offset.dx, 75.0 + distance + offset.dy);
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(startP.dx, startP.dy, endP.dx, endP.dy),
|
||||
SurfacePaintData()
|
||||
..strokeWidth = 1
|
||||
..color =
|
||||
0xFFFF9800 // orange
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
final Path path = Path();
|
||||
path.moveTo(startP.dx, startP.dy);
|
||||
path.arcToPoint(
|
||||
endP,
|
||||
rotation: 45,
|
||||
radius: const Radius.elliptical(40, 60),
|
||||
largeArc: largeArc,
|
||||
clockwise: clockwise,
|
||||
);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
SurfacePaintData()
|
||||
..strokeWidth = 2
|
||||
..color =
|
||||
0x61000000 // black38
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(emulateTesterEnvironment: false, setUpTestViewDimensions: false);
|
||||
|
||||
setUp(() async {
|
||||
debugShowClipLayers = true;
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
for (final DomNode scene in domDocument.querySelectorAll('flt-scene')) {
|
||||
scene.remove();
|
||||
}
|
||||
});
|
||||
|
||||
test('drawColor should cover entire viewport', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 400, 400);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture testPicture = _drawTestPicture(region, useColor: true);
|
||||
builder.addPicture(Offset.zero, testPicture);
|
||||
await sceneScreenshot(builder, 'canvas_draw_color', region: region);
|
||||
}, skip: true); // TODO(ferhat): matchGolden fails when a div covers viewport.
|
||||
|
||||
test('drawPaint should cover entire viewport', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 400, 400);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture testPicture = _drawTestPicture(region);
|
||||
builder.addPicture(Offset.zero, testPicture);
|
||||
await sceneScreenshot(builder, 'canvas_draw_paint', region: region);
|
||||
}, skip: true); // TODO(ferhat): matchGolden fails when a div covers viewport.);
|
||||
}
|
||||
|
||||
Picture _drawTestPicture(Rect region, {bool useColor = false}) {
|
||||
final EnginePictureRecorder recorder = PictureRecorder() as EnginePictureRecorder;
|
||||
const Rect r = Rect.fromLTWH(0, 0, 200, 200);
|
||||
final RecordingCanvas canvas = recorder.beginRecording(r);
|
||||
|
||||
canvas.drawRect(
|
||||
region.deflate(8.0),
|
||||
Paint() as SurfacePaint
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xFFE0E0E0),
|
||||
);
|
||||
|
||||
canvas.transform(Matrix4.translationValues(50, 50, 0).storage);
|
||||
|
||||
if (useColor) {
|
||||
canvas.drawColor(const Color.fromRGBO(0, 255, 0, 1), BlendMode.srcOver);
|
||||
} else {
|
||||
canvas.drawPaint(
|
||||
Paint() as SurfacePaint
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(0, 0, 255, 1),
|
||||
);
|
||||
}
|
||||
|
||||
canvas.drawCircle(
|
||||
Offset(r.width / 2, r.height / 2),
|
||||
r.width / 2,
|
||||
Paint() as SurfacePaint
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(255, 0, 0, 1),
|
||||
);
|
||||
|
||||
return recorder.endRecording();
|
||||
}
|
||||
@@ -1,836 +0,0 @@
|
||||
// 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 'dart:convert';
|
||||
import 'dart:js_util' as js_util;
|
||||
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';
|
||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(withImplicitView: true, setUpTestViewDimensions: false);
|
||||
|
||||
test('Paints image', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
rc.drawImage(createTestImage(), Offset.zero, SurfacePaint());
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_image');
|
||||
});
|
||||
|
||||
test(
|
||||
'Images from raw data are composited when picture is roundtripped through toImage',
|
||||
() async {
|
||||
final Uint8List imageData = base64Decode(base64PngData);
|
||||
final Codec codec = await instantiateImageCodec(imageData);
|
||||
final FrameInfo frameInfo = await codec.getNextFrame();
|
||||
codec.dispose();
|
||||
|
||||
const Rect bounds = Rect.fromLTRB(0, 0, 400, 300);
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas scratchCanvas = recorder.beginRecording(bounds);
|
||||
scratchCanvas.save();
|
||||
scratchCanvas.drawImage(frameInfo.image, Offset.zero, SurfacePaint());
|
||||
scratchCanvas.restore();
|
||||
final Picture picture = recorder.endRecording();
|
||||
final Image image = await picture.toImage(400, 300);
|
||||
|
||||
final RecordingCanvas rc = RecordingCanvas(bounds);
|
||||
rc.save();
|
||||
rc.drawImage(image, Offset.zero, SurfacePaint());
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_raw_image');
|
||||
},
|
||||
);
|
||||
|
||||
test('Paints image with transform', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
rc.translate(50.0, 100.0);
|
||||
rc.rotate(math.pi / 4.0);
|
||||
rc.drawImage(createTestImage(), Offset.zero, SurfacePaint());
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_image_with_transform');
|
||||
});
|
||||
|
||||
test('Paints image with transform and offset', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
rc.translate(50.0, 100.0);
|
||||
rc.rotate(math.pi / 4.0);
|
||||
rc.drawImage(createTestImage(), const Offset(30, 20), SurfacePaint());
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_image_with_transform_and_offset');
|
||||
});
|
||||
|
||||
test('Paints image with transform using destination', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
rc.translate(50.0, 100.0);
|
||||
rc.rotate(math.pi / 4.0);
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_image_rect_with_transform');
|
||||
});
|
||||
|
||||
test('Paints image with source and destination', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(testWidth / 2, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_image_rect_with_source');
|
||||
});
|
||||
|
||||
test('Paints image with source and destination and round clip', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
rc.save();
|
||||
rc.clipRRect(
|
||||
RRect.fromLTRBR(100, 30, 2 * testWidth, 2 * testHeight, const Radius.circular(16)),
|
||||
);
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(testWidth / 2, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_image_rect_with_source_and_clip');
|
||||
});
|
||||
|
||||
test('Paints image with transform using source and destination', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
rc.translate(50.0, 100.0);
|
||||
rc.rotate(math.pi / 6.0);
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(testWidth / 2, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_image_rect_with_transform_source');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/44845
|
||||
// Circle should draw on top of image not below.
|
||||
test('Paints on top of image', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.drawCircle(
|
||||
const Offset(100, 100),
|
||||
50.0,
|
||||
SurfacePaint()
|
||||
..strokeWidth = 3
|
||||
..color = const Color.fromARGB(128, 0, 0, 0),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_circle_on_image');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/44845
|
||||
// Circle should below image not on top.
|
||||
test('Paints below image', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
rc.drawCircle(
|
||||
const Offset(100, 100),
|
||||
50.0,
|
||||
SurfacePaint()
|
||||
..strokeWidth = 3
|
||||
..color = const Color.fromARGB(128, 0, 0, 0),
|
||||
);
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_circle_below_image');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/44845
|
||||
// Circle should draw on top of image with clip rect.
|
||||
test('Paints on top of image with clip rect', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
rc.clipRect(const Rect.fromLTRB(75, 75, 160, 160), ClipOp.intersect);
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.drawCircle(
|
||||
const Offset(100, 100),
|
||||
50.0,
|
||||
SurfacePaint()
|
||||
..strokeWidth = 3
|
||||
..color = const Color.fromARGB(128, 0, 0, 0),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_circle_on_image_clip_rect');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/44845
|
||||
// Circle should draw on top of image with clip rect and transform.
|
||||
test('Paints on top of image with clip rect with transform', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
// Rotate around center of circle.
|
||||
rc.translate(100, 100);
|
||||
rc.rotate(math.pi / 4.0);
|
||||
rc.translate(-100, -100);
|
||||
rc.clipRect(const Rect.fromLTRB(75, 75, 160, 160), ClipOp.intersect);
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.drawCircle(
|
||||
const Offset(100, 100),
|
||||
50.0,
|
||||
SurfacePaint()
|
||||
..strokeWidth = 3
|
||||
..color = const Color.fromARGB(128, 0, 0, 0),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_circle_on_image_clip_rect_with_transform');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/44845
|
||||
// Circle should draw on top of image with stack of clip rect and transforms.
|
||||
test('Paints on top of image with clip rect with stack', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
// Rotate around center of circle.
|
||||
rc.translate(100, 100);
|
||||
rc.rotate(-math.pi / 4.0);
|
||||
rc.save();
|
||||
rc.translate(-100, -100);
|
||||
rc.clipRect(const Rect.fromLTRB(75, 75, 160, 160), ClipOp.intersect);
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.drawCircle(
|
||||
const Offset(100, 100),
|
||||
50.0,
|
||||
SurfacePaint()
|
||||
..strokeWidth = 3
|
||||
..color = const Color.fromARGB(128, 0, 0, 0),
|
||||
);
|
||||
rc.restore();
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_circle_on_image_clip_rect_with_stack');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/44845
|
||||
// Circle should draw on top of image with clip rrect.
|
||||
test('Paints on top of image with clip rrect', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
rc.clipRRect(RRect.fromLTRBR(75, 75, 160, 160, const Radius.circular(5)));
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.drawCircle(
|
||||
const Offset(100, 100),
|
||||
50.0,
|
||||
SurfacePaint()
|
||||
..strokeWidth = 3
|
||||
..color = const Color.fromARGB(128, 0, 0, 0),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_circle_on_image_clip_rrect');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/44845
|
||||
// Circle should draw on top of image with clip rrect.
|
||||
test('Paints on top of image with clip path', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
final Path path = Path();
|
||||
// Triangle.
|
||||
path.moveTo(118, 57);
|
||||
path.lineTo(75, 160);
|
||||
path.lineTo(160, 160);
|
||||
rc.clipPath(path);
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
Rect.fromLTRB(100, 30, 2 * testWidth, 2 * testHeight),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.drawCircle(
|
||||
const Offset(100, 100),
|
||||
50.0,
|
||||
SurfacePaint()
|
||||
..strokeWidth = 3
|
||||
..color = const Color.fromARGB(128, 0, 0, 0),
|
||||
);
|
||||
rc.restore();
|
||||
await canvasScreenshot(rc, 'draw_circle_on_image_clip_path');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/53078
|
||||
// Verified that Text+Image+Text+Rect+Text composites correctly.
|
||||
// Yellow text should be behind image and rectangle.
|
||||
// Cyan text should be above everything.
|
||||
test('Paints text above and below image', () async {
|
||||
// Use a non-Ahem font so that text is visible.
|
||||
ui_web.debugEmulateFlutterTesterEnvironment = false;
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
rc.save();
|
||||
final Image testImage = createTestImage();
|
||||
final double testWidth = testImage.width.toDouble();
|
||||
final double testHeight = testImage.height.toDouble();
|
||||
const Color orange = Color(0xFFFF9800);
|
||||
final Paragraph paragraph1 = createTestParagraph(
|
||||
'Should be below below below below below',
|
||||
color: orange,
|
||||
);
|
||||
paragraph1.layout(const ParagraphConstraints(width: 400.0));
|
||||
rc.drawParagraph(paragraph1, const Offset(20, 100));
|
||||
rc.drawImageRect(
|
||||
testImage,
|
||||
Rect.fromLTRB(0, 0, testWidth, testHeight),
|
||||
const Rect.fromLTRB(100, 100, 200, 200),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.drawRect(
|
||||
const Rect.fromLTWH(50, 50, 100, 200),
|
||||
SurfacePaint()
|
||||
..strokeWidth = 3
|
||||
..color = const Color(0xA0000000),
|
||||
);
|
||||
const Color cyan = Color(0xFF0097A7);
|
||||
final Paragraph paragraph2 = createTestParagraph(
|
||||
'Should be above above above above above',
|
||||
color: cyan,
|
||||
);
|
||||
paragraph2.layout(const ParagraphConstraints(width: 400.0));
|
||||
rc.drawParagraph(paragraph2, const Offset(20, 150));
|
||||
rc.restore();
|
||||
await canvasScreenshot(
|
||||
rc,
|
||||
'draw_text_composite_order_below',
|
||||
region: const Rect.fromLTWH(0, 0, 350, 300),
|
||||
);
|
||||
});
|
||||
|
||||
// Creates a picture
|
||||
test('Paints nine slice image', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 500, 500);
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final Canvas canvas = Canvas(recorder, region);
|
||||
final Image testImage = createNineSliceImage();
|
||||
canvas.clipRect(const Rect.fromLTWH(0, 0, 420, 200));
|
||||
canvas.drawImageNine(
|
||||
testImage,
|
||||
const Rect.fromLTWH(20, 20, 20, 20),
|
||||
const Rect.fromLTWH(20, 20, 400, 400),
|
||||
SurfacePaint(),
|
||||
);
|
||||
final Picture picture = recorder.endRecording();
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.addPicture(Offset.zero, picture);
|
||||
|
||||
// Wrap in <flt-scene> so that our CSS selectors kick in.
|
||||
final DomElement sceneElement = createDomElement('flt-scene');
|
||||
if (isIosSafari) {
|
||||
// Shrink to fit on the iPhone screen.
|
||||
sceneElement.style.position = 'absolute';
|
||||
sceneElement.style.transformOrigin = '0 0 0';
|
||||
sceneElement.style.transform = 'scale(0.3)';
|
||||
}
|
||||
try {
|
||||
sceneElement.append(builder.build().webOnlyRootElement!);
|
||||
domDocument.body!.append(sceneElement);
|
||||
await matchGoldenFile('draw_nine_slice.png', region: region);
|
||||
} finally {
|
||||
// The page is reused across tests, so remove the element after taking the
|
||||
// screenshot.
|
||||
sceneElement.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/78068
|
||||
// Tests for correct behavior when using drawImageNine with a destination
|
||||
// size that is too small to render the center portion of the original image.
|
||||
test('Paints nine slice image', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 100, 100);
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final Canvas canvas = Canvas(recorder, region);
|
||||
final Image testImage = createNineSliceImage();
|
||||
canvas.clipRect(const Rect.fromLTWH(0, 0, 100, 100));
|
||||
// The testImage is 60x60 and the center slice is 20x20 so the edges
|
||||
// of the image are 40x40. Drawing into a destination that is smaller
|
||||
// than that will not provide enough room to draw the center portion.
|
||||
canvas.drawImageNine(
|
||||
testImage,
|
||||
const Rect.fromLTWH(20, 20, 20, 20),
|
||||
const Rect.fromLTWH(20, 20, 36, 36),
|
||||
SurfacePaint(),
|
||||
);
|
||||
final Picture picture = recorder.endRecording();
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.addPicture(Offset.zero, picture);
|
||||
|
||||
// Wrap in <flt-scene> so that our CSS selectors kick in.
|
||||
final DomElement sceneElement = createDomElement('flt-scene');
|
||||
if (isIosSafari) {
|
||||
// Shrink to fit on the iPhone screen.
|
||||
sceneElement.style.position = 'absolute';
|
||||
sceneElement.style.transformOrigin = '0 0 0';
|
||||
sceneElement.style.transform = 'scale(0.3)';
|
||||
}
|
||||
try {
|
||||
sceneElement.append(builder.build().webOnlyRootElement!);
|
||||
domDocument.body!.append(sceneElement);
|
||||
await matchGoldenFile('draw_nine_slice_empty_center.png', region: region);
|
||||
} finally {
|
||||
// The page is reused across tests, so remove the element after taking the
|
||||
// screenshot.
|
||||
sceneElement.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/61691
|
||||
//
|
||||
// The bug in bitmap_canvas.dart was that when we transformed and clipped
|
||||
// the image we did not apply `transform-origin: 0 0 0` to the clipping
|
||||
// element which resulted in an undesirable offset.
|
||||
test('Paints clipped and transformed image', () async {
|
||||
const Rect region = Rect.fromLTRB(0, 0, 60, 70);
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
canvas.translate(10, 10);
|
||||
canvas.transform(Matrix4.rotationZ(0.4).storage);
|
||||
canvas.clipPath(
|
||||
Path()
|
||||
..moveTo(10, 10)
|
||||
..lineTo(50, 10)
|
||||
..lineTo(50, 30)
|
||||
..lineTo(10, 30)
|
||||
..close(),
|
||||
);
|
||||
canvas.drawImage(createNineSliceImage(), Offset.zero, SurfacePaint());
|
||||
await canvasScreenshot(canvas, 'draw_clipped_and_transformed_image', region: region);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/61245
|
||||
test('Should render image with perspective', () async {
|
||||
const Rect region = Rect.fromLTRB(0, 0, 200, 200);
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
canvas.translate(10, 10);
|
||||
canvas.drawImage(createTestImage(), Offset.zero, SurfacePaint());
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..setRotationY(0.8)
|
||||
..setEntry(3, 2, 0.0005); // perspective
|
||||
canvas.transform(transform.storage);
|
||||
canvas.drawImage(createTestImage(), const Offset(0, 100), SurfacePaint());
|
||||
await canvasScreenshot(canvas, 'draw_3d_image', region: region, setupPerspective: true);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/61245
|
||||
test('Should render image with perspective inside clip area', () async {
|
||||
const Rect region = Rect.fromLTRB(0, 0, 200, 200);
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
canvas.drawRect(region, SurfacePaint()..color = const Color(0xFFE0E0E0));
|
||||
canvas.translate(10, 10);
|
||||
canvas.drawImage(createTestImage(), Offset.zero, SurfacePaint());
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..setRotationY(0.8)
|
||||
..setEntry(3, 2, 0.0005); // perspective
|
||||
canvas.transform(transform.storage);
|
||||
canvas.clipRect(region, ClipOp.intersect);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(0, 0, 100, 200),
|
||||
SurfacePaint()..color = const Color(0x801080E0),
|
||||
);
|
||||
canvas.drawImage(createTestImage(), const Offset(0, 100), SurfacePaint());
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(50, 150, 50, 20),
|
||||
SurfacePaint()..color = const Color(0x80000000),
|
||||
);
|
||||
await canvasScreenshot(canvas, 'draw_3d_image_clipped', region: region, setupPerspective: true);
|
||||
});
|
||||
|
||||
test('Should render rect with perspective transform', () async {
|
||||
const Rect region = Rect.fromLTRB(0, 0, 400, 400);
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
canvas.drawRect(region, SurfacePaint()..color = const Color(0xFFE0E0E0));
|
||||
canvas.translate(20, 20);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(0, 0, 100, 40),
|
||||
SurfacePaint()..color = const Color(0xFF000000),
|
||||
);
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..setRotationY(0.8)
|
||||
..setEntry(3, 2, 0.001); // perspective
|
||||
canvas.transform(transform.storage);
|
||||
canvas.clipRect(region, ClipOp.intersect);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(0, 60, 120, 40),
|
||||
SurfacePaint()..color = const Color(0x801080E0),
|
||||
);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(300, 250, 120, 40),
|
||||
SurfacePaint()..color = const Color(0x80E010E0),
|
||||
);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(const Rect.fromLTWH(0, 120, 160, 40), const Radius.circular(5)),
|
||||
SurfacePaint()..color = const Color(0x801080E0),
|
||||
);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(const Rect.fromLTWH(300, 320, 90, 40), const Radius.circular(20)),
|
||||
SurfacePaint()..color = const Color(0x80E010E0),
|
||||
);
|
||||
await canvasScreenshot(canvas, 'draw_3d_rect_clipped', region: region, setupPerspective: true);
|
||||
});
|
||||
|
||||
test('Should render color and ovals with perspective transform', () async {
|
||||
const Rect region = Rect.fromLTRB(0, 0, 400, 400);
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
canvas.drawRect(region, SurfacePaint()..color = const Color(0xFFFF0000));
|
||||
canvas.drawColor(const Color(0xFFE0E0E0), BlendMode.src);
|
||||
canvas.translate(20, 20);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(0, 0, 100, 40),
|
||||
SurfacePaint()..color = const Color(0xFF000000),
|
||||
);
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..setRotationY(0.8)
|
||||
..setEntry(3, 2, 0.001); // perspective
|
||||
canvas.transform(transform.storage);
|
||||
canvas.clipRect(region, ClipOp.intersect);
|
||||
canvas.drawOval(
|
||||
const Rect.fromLTWH(0, 120, 130, 40),
|
||||
SurfacePaint()..color = const Color(0x801080E0),
|
||||
);
|
||||
canvas.drawOval(
|
||||
const Rect.fromLTWH(300, 290, 90, 40),
|
||||
SurfacePaint()..color = const Color(0x80E010E0),
|
||||
);
|
||||
canvas.drawCircle(const Offset(60, 240), 50, SurfacePaint()..color = const Color(0x801080E0));
|
||||
canvas.drawCircle(const Offset(360, 370), 30, SurfacePaint()..color = const Color(0x80E010E0));
|
||||
await canvasScreenshot(canvas, 'draw_3d_oval_clipped', region: region, setupPerspective: true);
|
||||
});
|
||||
|
||||
test('Should render path with perspective transform', () async {
|
||||
const Rect region = Rect.fromLTRB(0, 0, 400, 400);
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
canvas.drawRect(region, SurfacePaint()..color = const Color(0xFFFF0000));
|
||||
canvas.drawColor(const Color(0xFFE0E0E0), BlendMode.src);
|
||||
canvas.translate(20, 20);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(0, 0, 100, 20),
|
||||
SurfacePaint()..color = const Color(0xFF000000),
|
||||
);
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..setRotationY(0.8)
|
||||
..setEntry(3, 2, 0.001); // perspective
|
||||
canvas.transform(transform.storage);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(0, 120, 130, 40),
|
||||
SurfacePaint()..color = const Color(0x801080E0),
|
||||
);
|
||||
canvas.drawOval(
|
||||
const Rect.fromLTWH(300, 290, 90, 40),
|
||||
SurfacePaint()..color = const Color(0x80E010E0),
|
||||
);
|
||||
final Path path = Path();
|
||||
path.moveTo(50, 50);
|
||||
path.lineTo(100, 50);
|
||||
path.lineTo(100, 100);
|
||||
path.close();
|
||||
canvas.drawPath(path, SurfacePaint()..color = const Color(0x801080E0));
|
||||
|
||||
canvas.drawCircle(const Offset(50, 50), 4, SurfacePaint()..color = const Color(0xFF000000));
|
||||
canvas.drawCircle(const Offset(100, 100), 4, SurfacePaint()..color = const Color(0xFF000000));
|
||||
canvas.drawCircle(const Offset(100, 50), 4, SurfacePaint()..color = const Color(0xFF000000));
|
||||
await canvasScreenshot(canvas, 'draw_3d_path', region: region, setupPerspective: true);
|
||||
});
|
||||
|
||||
test('Should render path with perspective transform', () async {
|
||||
const Rect region = Rect.fromLTRB(0, 0, 400, 400);
|
||||
final RecordingCanvas canvas = RecordingCanvas(region);
|
||||
canvas.drawRect(region, SurfacePaint()..color = const Color(0xFFFF0000));
|
||||
canvas.drawColor(const Color(0xFFE0E0E0), BlendMode.src);
|
||||
canvas.translate(20, 20);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(0, 0, 100, 20),
|
||||
SurfacePaint()..color = const Color(0xFF000000),
|
||||
);
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..setRotationY(0.8)
|
||||
..setEntry(3, 2, 0.001); // perspective
|
||||
canvas.transform(transform.storage);
|
||||
//canvas.clipRect(region, ClipOp.intersect);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(0, 120, 130, 40),
|
||||
SurfacePaint()..color = const Color(0x801080E0),
|
||||
);
|
||||
canvas.drawOval(
|
||||
const Rect.fromLTWH(300, 290, 90, 40),
|
||||
SurfacePaint()..color = const Color(0x80E010E0),
|
||||
);
|
||||
final Path path = Path();
|
||||
path.moveTo(50, 50);
|
||||
path.lineTo(100, 50);
|
||||
path.lineTo(100, 100);
|
||||
path.close();
|
||||
canvas.drawPath(path, SurfacePaint()..color = const Color(0x801080E0));
|
||||
|
||||
canvas.drawCircle(const Offset(50, 50), 4, SurfacePaint()..color = const Color(0xFF000000));
|
||||
canvas.drawCircle(const Offset(100, 100), 4, SurfacePaint()..color = const Color(0xFF000000));
|
||||
canvas.drawCircle(const Offset(100, 50), 4, SurfacePaint()..color = const Color(0xFF000000));
|
||||
await canvasScreenshot(canvas, 'draw_3d_path_clipped', region: region, setupPerspective: true);
|
||||
});
|
||||
}
|
||||
|
||||
// 9 slice test image that has a shiny/glass look.
|
||||
const String base64PngData =
|
||||
'iVBORw0KGgoAAAANSUh'
|
||||
'EUgAAADwAAAA8CAYAAAA6/NlyAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPo'
|
||||
'AAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAApGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQA'
|
||||
'AARoABQAAAAEAAABKARsABQAAAAEAAABSATEAAgAAACAAAABah2kABAAAAAEAAAB6AAAAAAAA'
|
||||
'AEgAAAABAAAASAAAAAFBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpAAADoAEAAwAA'
|
||||
'AAEAAQAAoAIABAAAAAEAAAA8oAMABAAAAAEAAAA8AAAAAKgRPeEAAAAJcEhZcwAACxMAAAs'
|
||||
'TAQCanBgAAATqaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOn'
|
||||
'g9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQuMCI+CiAgIDxyZGY6Uk'
|
||||
'RGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW'
|
||||
'5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAg'
|
||||
'IHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgICA'
|
||||
'gICAgICB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1J'
|
||||
'lc291cmNlUmVmIyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlL'
|
||||
'mNvbS9leGlmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2'
|
||||
'JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmF'
|
||||
'kb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8eG1wTU06SW5zdGFuY2VJRD54bXAua'
|
||||
'WlkOjMxRTc0MTc5ODQwQTExRUE5OEU4QUI4OTRCMjhDRUE3PC94bXBNTTpJbnN0YW5jZUl'
|
||||
'EPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD54bXAuZGlkOjMxRTc0MTdBODQwQTExR'
|
||||
'UE5OEU4QUI4OTRCMjhDRUE3PC94bXBNTTpEb2N1bWVudElEPgogICAgICAgICA8eG1wTU0'
|
||||
'6RGVyaXZlZEZyb20gcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICA8c'
|
||||
'3RSZWY6aW5zdGFuY2VJRD54bXAuZGlkOjAxODAxMTc0MDcyMDY4MTE4MjJBQUI1NDhBQTA'
|
||||
'zMDNBPC9zdFJlZjppbnN0YW5jZUlEPgogICAgICAgICAgICA8c3RSZWY6ZG9jdW1lbnRJR'
|
||||
'D54bXAuZGlkOjAxODAxMTc0MDcyMDY4MTE4MjJBQUI1NDhBQTAzMDNBPC9zdFJlZjpkb2N'
|
||||
'1bWVudElEPgogICAgICAgICA8L3htcE1NOkRlcml2ZWRGcm9tPgogICAgICAgICA8ZXhpZ'
|
||||
'jpQaXhlbFlEaW1lbnNpb24+NjA8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICAgICA8'
|
||||
'ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl'
|
||||
'4ZWxYRGltZW5zaW9uPjYwPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPHhtcD'
|
||||
'pDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpPC94bXA6Q3Jl'
|
||||
'YXRvclRvb2w+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YX'
|
||||
'Rpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZ'
|
||||
'XRhPgpq1fpCAAAUDUlEQVRoBd1beYxd11n/3fvu2+fNm9VbPB6PHS+ldt0kNKWJk0YkaVJS'
|
||||
'UtRQhFCqSggRCVWgSKUKEgi1FNSKlvIH/1dVoWqatBKkUYqgAVRaNSTN0jg2buIkE4/XmbF'
|
||||
'ne/O2u/D7ffedN2/GM9hJCRAf+76z3LN8+/nuOd94jUYjwRWmJFnp6nkeVI+jCJ7vw+Mc9l'
|
||||
'Z9+M7qLK+MYHPPOmrvjulp17yunxvr+inXWmvh6Bl+2WJw2R7qwJUFehLHiIUEH58LWxIAa'
|
||||
'RerOoBi1Qi8S71AujaXW69O314k3XvXprURhrZ+ijyJ45HYIlLPWm7cevkVIUw+GieThFzN'
|
||||
'pAt0EUj0LiYQRNFBxnISs2L/00YRa6NkwHYAdsCLoBqpx7V5WjuTWZEMrat5faIvBgjxjRb'
|
||||
'ptG+IsKjJ6ToU5UTZLFdPENaXUL+4gNqFi1iuMT8/jcb8AtqkfBxHQNREVG8CzTaLbbaJGJ'
|
||||
'KKlCApQCkRCCHXELycvyMx2VyWawUI8jnEhQIJnEEuk0U2F6AyOopCdQil/ipKQwMsV+GrH'
|
||||
'9UqabdJjAAZzuWLABukdREWgB5hSjSRR4py0fq5aUwdfxFTzzyDsz9+FrOPfR9NLIIoot2Z'
|
||||
'XMsIAeUSeL1zybWpvaMMxr2QdY1RUh89SkYI5qobl5ln+JDsKO89hNFbb8Tm91+P7e+9Hps'
|
||||
'm9qBQLiNutZAE0vFUCtn1kuStZ7SEcGgUyyBi/tpTT+HFb3wTk1/7KuqcQgBnc2UEWzfD6+'
|
||||
'9HJiBlCZokQhSWCIbkaIbzrKRU3AwBa+6gwQaSl91Y55iI3JFeKgkOgo6oIyVeo4XW0hzCU1'
|
||||
'MkdkqUYQxj3xc+jet+5R4M7tlt3M6SST6ZtF5ajTAXSAhs1A4RZTy0KarP/O3f4Sd/+Gksc3'
|
||||
'Rx/FrkByrItglio4FQFK0vI9PiuM4/P6KeCxjqcIa8T/wM5yTYFAOhLNSka8SG/YigyRgBZ'
|
||||
'LNPmfBi9ucEIl7skf8Udb2LcszzeVI6i7DAvEBRZvvykRfQYr7pPdfjji98Hrs+eJidfWS'
|
||||
'yGseRtiA7dNIqhEXRiIaACoX6wgU8+eW/xgtf+kuUt4wjPzyIzMISsFgzg5TJS9cyCKg3YC'
|
||||
'6xFzdowmwNQ81LBVPlkDoiQiipn9W4XtBtVRvnYJtgpDLxl/NJ9yU1VK+wSQvdjii6DUQt2g'
|
||||
'zCkAz2o03k54++aCL/kUe+hQN3302KUZcFX8c2aF2lVQgL2TBu09408fRX/gY//LPPoXLgAI'
|
||||
'pL5OTcIjIUt6BYQEA9aS/MIbk4Y3oqJkmAhJ7AdA+LloSA3qdcTHVbZSVxx6UURVdL+7ua7I'
|
||||
'HGxPz1N29DQm5HFHEZSEljuGkAzZ+d5HzL+NgTj2P/bXcgCVuENTAVc/N0ERZ3k4hL+hGe'
|
||||
'feQRPPHJ30bfwQMoTC8gaDSJaAl+u4Vw5hS5QvHGZhQPvxulrVuR3TSKbLmIbDGPDAHJUI'
|
||||
'yzfLiaGT0ZvtinFSWnZJGFmCVxU1RSYqNHNUgS8pY6G4qjXC+hpW8vN9BcmEfz7HnUJ09h6'
|
||||
'cWnTIdt3NZxtGln2st1tLaOoH7iZTNsn3j+WWzet5/UpS0hHC51EY6ikJIc4OzRo/jmDdcDW'
|
||||
'8ZRlvI3G0SmBO/kKzbRtt/8HYzdfhjl8TEUaLBy+QK8XJ6IkPJEJvYFuf6rLN6yxP3bF/9FVN'
|
||||
'HUkDahVsVgERH06Fc7hDp6FEuVI4q1CBHRZoRUqcbsBUwdewlnHv4Ozr3wY2B4K9qUunB+Ccn'
|
||||
'EGM4fO4J9n/wEPvqlv0Kxr2xwS02VDGFxty0LWW/gH//i8zjy5a9g9F0H4J2aRXagD94bL2P'
|
||||
'gF2/FdZ96AJsOXY+gr4iWWBNSwORgaPNn0jwCPwV8bSFFTP0um9h1ZR4irYp++Lj9Wos0Z8/'
|
||||
'jlSe+h6N//lkkpVE0Kjk0l+po7tiGaSJ936OP4roP30N1Ipwdq20Ia+OOyd3Xf/IMvn34ZpR'
|
||||
'370d2oYY8N/XM1AmM3H433vfQZ9A3MU7Pjtok4yEgmDLCziCy6srPem0rby9T4qRdqnW6SjS'
|
||||
'6RXJcHCvSIaERO/kvT+KpT/0eGpu3o91oo1XKYe7MSQzdchPu//o3UN60CVnBw8f4LP5EyzW'
|
||||
'89Njjphs5ikeWRPDZVkIfbvj9P0Dfrp2o1SgyNAQJrV9Cj8jn9uDTG5LeasvQk2GbPTkSa9WT'
|
||||
'RcAxAdvWe1b37dgCN5fNW7C1wPF+Nk8YfLTqNdQpmdtvvxPv+tPPwj83hWy1j/ksqvt/Aad/'
|
||||
'8COcePbZVNU6BAu0X3o0MLOnT+O1L34RA9vGgLMzFOV+RJOvYNdDf4zKnmvRpEUsFqgPpKwM'
|
||||
'SkxDIXeuVadehU3zcvw2jY30je9lLELqnSRfap0ktBHGto5odLglQybeaf+XcZMPrZ3EV5kup'
|
||||
'lW05Yl4lDgvTycnyCMfpMRLWnRhucbE7bfh1D/cjOnnfojc5jE6KEvgbo3nHvt77D98C/J9VE'
|
||||
'3CRjOq7d/HGz990RzFUb7IzNW57yU2YHTvPhLEQ5YTty7OYuH0edROT2HxzHksT0/TanNrWp'
|
||||
'xDe47cX2zS11623FukJCR8QPE357PWQXF1JvRTYSUHUWaN+zslIdNHJ7JShkfxDCpFeCznRk'
|
||||
'eQHxlFfvMI+rdsQ9+27SiMDFm/fHUE2266CTNEOEvCtJeX0Ld1DCe/+jVMPfAA9hy6zvyHQK'
|
||||
'Itakz++w8ovtRJcjJDKnr1OnJEeenlE4j4YbA4OYmZ53+Kucef4t57qrt/Sie0RyoJcO23ZA'
|
||||
'vLFD0CH5M7Cbnho2rt9lqdzNCZL9SxwtSvkO5YXIe45l2gpFxIZ'
|
||||
'+6VCdNBDtdGk9/3Pgx/4L0YPXgQxf4BxPQM5WuH3MpydNKSoQriM8Dkc89j97sPmsGjfPhYmj'
|
||||
'mLcw8/TiM1iICdNalPDicDI3j16w8jnJqhDz1tiGWE0vZxUrFIv5nOIMXEo5RojMRX24dhTgd'
|
||||
'eX1zypyW06cceO61JtLv2jjuQ9dIuZS3K9dCpIOrpO9vCKJE0nK1aDc3jT2ORz0nOWdq6D+0'
|
||||
'cR1a2GNPkoVFfDa43/uMZ1D92H8qVCrzlZjM5+q//hu/e9SEM7NqNwlwNOQJugAj2ptx0cmKE'
|
||||
'nfWJSD2FfGhZa+k/ERK3TCyJtRx/NbHRxqm0cXJ9lKcIC71VqbdK5ZauewFdWT6g6CbcqCOq'
|
||||
'UXxhiW2UzCLbOEEzDtEqF7AwfQFLYQ2/+9JRXLN7F4KQYnTu1RO2TBDkKJ+LhDoVnFgISI8'
|
||||
'oBUmDulhr2GQihv6TiUysSWytlKKZtkpuV5J7v9KikuvjciG9Ura+a6pqk1GM6SiBHqDsix'
|
||||
'm3kQF4VAUdPEgyJH0+9+T87u04f/w4zk9NYsvuCaoNnY25o/9psi+vxp1U2MRCih/1ssgxRV'
|
||||
'WeU8JHhDAR5Hvxtlvu1CWAaut9tG/31n+uMtcXHInEXfPSxoDISuJc0geKT8Low1X6fvZnL5'
|
||||
'sP4dcWFzFNdpO39JWlv5yB/7splc9utbcgz8rkd02flWV7e7/9ZVu3A7syfiSa/y+Ep0+8h'
|
||||
'naL3wR1OuW1J/8J5YBWtNmyTdph3MV7DUIOdHsv+VHq9LncmLTz2/TrQOnAI//eq9Vt95k5'
|
||||
'dgx17kZ+/eK8MdTbOgyPCK/i7tsE19s9rSO6T5H36AbnCv1Y+NHT/MZfhL8wP297aoZfPT6'
|
||||
'3pKspybhm6GsHg1V+Zc3y0HERwdJSymFRQ6ZW1s1R6J2MvFxWJV9+An1xmrVUpBsLC7YRyD'
|
||||
'lQuhqQNTy0XxtGxEkWneXaInUYy/R9WdnALnWGvDOzlIVyQ7lFEYUGz+T8Fo9GVPG43zqKv'
|
||||
'DPRuxRq4SM+K5f/0tYu1KYVU0VOR/d8ifWrIUmP0zPu9CRVx1h+yA96S5JpfYhepcmJN7+3'
|
||||
'13FWr0Kknbr6Olq1REvmDuOuJnx1iZd+oBIrougnvJW7WpPEWB8yxNl2oozO4YKgkH6B6'
|
||||
'VqMMSZedj6HDRL/EMK1Xoq9VgEV2e8gm7YqmEIMtjD6WYp4q6W9WxTIo6aSPyXElyJtD1de'
|
||||
'PWtrv3byV3c77JsYYTz6o1PF/q5+VfP48vWYm4KWeJsB3h2P6Vyv2Vzt8Lj3ltbOhtu9J5Nu'
|
||||
'r3VmhnBxHEqT27wKNbHsaXijzbrvJCm6skPPtJdA6s9D8JaTrj/8mvpDWiUQ7jeeQOvAdFHl'
|
||||
'f55SJv8m+5mZfbPMsyhFNavhmKOvr0jlH553lEITf+rVLL4kt4gC/Xqn9iAkXdgOapyKXde'
|
||||
'wIVmfIzqZJHHqTXaeSYi7vHu2oredfeqTKFjvJ1CE/H+4Lb/rpGaf5V63XA4cdMwlQfQ7qU'
|
||||
'LZlsIkQhqriI6VEK6OjdNg8bYiy7uh4vgOzLOR0Qr8Hu6cMffI9XqUXk0OrdKTCJCS/XbKPW'
|
||||
'+vqNg7f+9J7aWD2ZP/hapbTwRSBEEcMHwjatshXmnnNt5pMToox7vd/p3j4AE974dChDwICH'
|
||||
'gIL0vtPqJF3bVxVu72sBcAfYAouXEa48rpmyv/dWM1pYU89Qx163SbiK04re8iJ2G6IIh5R'
|
||||
'h2dImaVKoZ4NROQ2zzSDVBlCEFmUxWtmQWGD+iMORUNi9USsppZTb2Prdbb4Lb3NE/dOQL'
|
||||
'AwQY0+7u2NBeQnXcu5wLuncRT45TU1ru4DuN7m9z8UhuVI74nrS0sohW3UPrQHRgYHGa8V6'
|
||||
'B7Yg+VkWEUb72LFwoXKAZB96xZYuGe9HaPQAgQW9Atqjxti7WQTheUs9mdReuGSO9c3eVS'
|
||||
'IYmry1172ldzpHOZlLCsddJHc6fv3Fpaz82j4xydnbfZh2E4GDnIOJUKr1HpSfoBbxWK1'
|
||||
'KKNxwy6oQy4zzUtkGUnbjzRMwveQRshygmSoZAR3/Y7g7bDZGefqZfHOsQdHnaznFr+ro1'
|
||||
'uvkaWLowchxjewymNnfYBu+PtdFu2stwK7qVijgKsgzry+eKGL12L86igIhnXHGO1lp3uy'
|
||||
'Y3HLFhkiymfpm68j+XdD/M1eCSvXAV5j39LxnX02394trJ0l5SPr0JeXqjWK7WuTMofeTDG'
|
||||
'LyG16q6jKf08nM4QIk36iOMxqnc/xsI52bQYrBKm8FmERUw1UHd/W3wEFpxSAtJPJVbvdN'
|
||||
'9d62dfpfMs712SgnLPKOHWwar3JIfrYTRg82Y7R40SZV2sywh/7BIV6g826F6qbDeeR4E5e'
|
||||
'rVjB00wcs5KHNOK2Yoq29zMSL3I50t0R0bPJVeSqC6bv/rbIIm65lcNH6RXrYpliz9kAJjT'
|
||||
'cmkf2lG7HlwCEUc4ot40U7CcCditsQvZH+fBmje/bi9Md/HfVHHkXmGsY/zTUQkHLupNr7f'
|
||||
'3bo1dk/bEvSNiaDGRLGNiP0xN2xe+/F8PAoSgqmoxcp48eQKSJM2S4WixjkCf3YnXebwVI8'
|
||||
'FDWd5l2UpMUU9Yh8Wk7F14mSxOmyj8Tuv3s2msPGCIbV4yPqjcGinC8l4gpQi4YraJycRPH'
|
||||
'u7Cd3O1jjFmRCGfMi5RUE2FVioxXLPNrYnRiJzY9+CAWF2YQ9pWIPENEOaFy3YFbmYuEsUSe'
|
||||
'Wxj3git5Qo3Z4Iloztefg3rKdwqUSccShs4cEY1lyMjUFoNR236W8doeWoMlLE/xI4jE2Xnf'
|
||||
'r6I6NESEKwz8YfgFtyRjLt9ZIS+xrvRjqbKI8cO3YplUmnv0O8iN74Q/v8xbt4YFbsu8amB'
|
||||
'qxVlTiM6VpMt16xj71VOlQmsxWRIhCarrR5ui9sjur+maDJQZ2dvkzf8CJj73JxjZuxvV/g'
|
||||
'rKjKPuPai070EhoEi1EsW6f6CKBm/Wd37013Bsjlep//x9ZHdsZ1QNY6Pma3bJbArCMRrH'
|
||||
'7T81zbIITGpJk9OwtIVbZ'
|
||||
'/dNp0M3036d0iP91QvNopo9Ukg3P8v2wWAv6CuXaIz4TR9OMoaaXtXYZx7EjusY7FIYRJkI'
|
||||
'Z/XBQJV1qYuwsZsvyuU+9FWXMRBuw+77fwuvV4ex8O1vGY4Bg1kUkp8wfkLy7dH39hW84hE'
|
||||
'KKrhCfB2atgDFz074VaFYbpgkMcJI/V1im7Z4kyb9rQPjtuRTh5QoT1sMjVDCLTVcpN/w2qt'
|
||||
'kvI+df/QQdrz/RnJ2AGXaozIZmMsytkvi3Jm3i7omli6X+PU0PDBkccnJNWMIPn4fzk3swpn'
|
||||
'vfRf1I0eM4hokAvtFXqLn6aRkeDhGZ0VzWFwIraVO/HVzJ7ETB21AZ9G1meGr+WR9mDS3mC'
|
||||
'pLpQhbyi2LJDJvDqKFOkMw0vsw9VaYUumee3HNbb/M6NkJDPDIapB/DzHAs7oU2RWZ05Tda'
|
||||
'Fr3nRuRS8u8YJtnZMDsLAPReJ1aUxzI1CnMv/o65l9/A0tnX0Vy/gKi4ycMOIePANAjgJ'
|
||||
'cu8pO9VTWe0dxV1/bpnb10TjldirDPO4roHDttciMbEFpxziqu8YxvHMC5aFhcpTMoqEaH'
|
||||
'BxEf9+KsZLBcqmLsBqEtE4JZN6XGZi2xNjKixcY9sNQgTrDl5o892rTMLQajHHk+5CE8di'
|
||||
'fDKCVVXgvDwLptJiOcT7N5XTXglLdqmtQ1l+h6EqTYmFjJR0KZc5QlBWhY5F5BjP70bgqnD'
|
||||
'nPHSVH9zHPrbNEH6LET0HZn37+xUu5xG8DSqp0V7D0ItwVacEikdSjjjJgoqwuGvXNXGOg'
|
||||
'Z2x0yEXi0PeqFO87AiFGCkemvOKYkSF/6yS1vnLOWTaHN/VcmnSWt0eHThELBHBiCGisGra'
|
||||
'SI5l6R3qD0q05ZR4TNVHES7bnltEgcg6JF3uVlyFsBpdB+lzgdRTMHeejneZR0H1BrnKECH'
|
||||
'7GyVy1BAmcsr17WxYs78QNqQF4bppFXqX9BBqIrzmcExwueASjAFzlaWncpqEpJCXVc7wv'
|
||||
'Nj7eT/BbztCaofk+k0AAAAAyBMj8AAAAAElFTkSuQmCC';
|
||||
const String base64ImageUrl = 'data:image/png;base64,$base64PngData';
|
||||
|
||||
HtmlImage createNineSliceImage() {
|
||||
return HtmlImage(createDomHTMLImageElement()..src = base64ImageUrl, 60, 60);
|
||||
}
|
||||
|
||||
HtmlImage createTestImage({int width = 100, int height = 50}) {
|
||||
final DomCanvasElement canvas = createDomCanvasElement(width: width, height: height);
|
||||
final DomCanvasRenderingContext2D ctx = canvas.context2D;
|
||||
ctx.fillStyle = '#E04040';
|
||||
ctx.fillRect(0, 0, 33, 50);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#40E080';
|
||||
ctx.fillRect(33, 0, 33, 50);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#2040E0';
|
||||
ctx.fillRect(66, 0, 33, 50);
|
||||
ctx.fill();
|
||||
final DomHTMLImageElement imageElement = createDomHTMLImageElement();
|
||||
imageElement.src = js_util.callMethod<String>(canvas, 'toDataURL', <dynamic>[]);
|
||||
return HtmlImage(imageElement, width, height);
|
||||
}
|
||||
|
||||
Paragraph createTestParagraph(String text, {Color color = const Color(0xFF000000)}) {
|
||||
final ParagraphBuilder builder = ParagraphBuilder(
|
||||
ParagraphStyle(
|
||||
fontFamily: 'Roboto',
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
);
|
||||
builder.pushStyle(TextStyle(color: color));
|
||||
builder.addText(text);
|
||||
return builder.build();
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
const Rect region = Rect.fromLTWH(0, 0, 500, 100);
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
SurfacePaint makePaint() => Paint() as SurfacePaint;
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(emulateTesterEnvironment: false, setUpTestViewDimensions: false);
|
||||
|
||||
setUpAll(() async {
|
||||
debugShowClipLayers = true;
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
});
|
||||
|
||||
group('Add picture to scene', () {
|
||||
test('draw growing picture across frames', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushClipRect(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
|
||||
_drawTestPicture(builder, 100, false);
|
||||
builder.pop();
|
||||
|
||||
final DomElement elm1 = builder.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(elm1);
|
||||
|
||||
// Now draw picture again but at larger size.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.pushClipRect(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
// Now draw the picture at original target size, which will use a
|
||||
// different code path that should normally not have width/height set
|
||||
// on image element.
|
||||
_drawTestPicture(builder2, 20, false);
|
||||
builder2.pop();
|
||||
|
||||
elm1.remove();
|
||||
await sceneScreenshot(builder2, 'canvas_draw_picture_acrossframes', region: region);
|
||||
});
|
||||
|
||||
test('draw growing picture across frames clipped', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushClipRect(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
|
||||
_drawTestPicture(builder, 100, true);
|
||||
builder.pop();
|
||||
|
||||
final DomElement elm1 = builder.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(elm1);
|
||||
|
||||
// Now draw picture again but at larger size.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.pushClipRect(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
_drawTestPicture(builder2, 20, true);
|
||||
builder2.pop();
|
||||
|
||||
elm1.remove();
|
||||
await sceneScreenshot(builder2, 'canvas_draw_picture_acrossframes_clipped', region: region);
|
||||
});
|
||||
|
||||
test('PictureInPicture', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture greenRectPicture = _drawGreenRectIntoPicture();
|
||||
|
||||
final EnginePictureRecorder recorder = PictureRecorder() as EnginePictureRecorder;
|
||||
final RecordingCanvas canvas = recorder.beginRecording(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
canvas.drawPicture(greenRectPicture);
|
||||
builder.addPicture(const Offset(10, 10), recorder.endRecording());
|
||||
|
||||
await sceneScreenshot(builder, 'canvas_draw_picture_in_picture_rect', region: region);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
HtmlImage? sharedImage;
|
||||
|
||||
void _drawTestPicture(SceneBuilder builder, double targetSize, bool clipped) {
|
||||
sharedImage ??= _createRealTestImage();
|
||||
final EnginePictureRecorder recorder = PictureRecorder() as EnginePictureRecorder;
|
||||
final RecordingCanvas canvas = recorder.beginRecording(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
canvas.debugEnforceArbitraryPaint();
|
||||
if (clipped) {
|
||||
canvas.clipRRect(RRect.fromLTRBR(0, 0, targetSize, targetSize, const Radius.circular(4)));
|
||||
}
|
||||
canvas.drawImageRect(
|
||||
sharedImage!,
|
||||
const Rect.fromLTWH(0, 0, 20, 20),
|
||||
Rect.fromLTWH(0, 0, targetSize, targetSize),
|
||||
makePaint(),
|
||||
);
|
||||
final Picture picture = recorder.endRecording();
|
||||
builder.addPicture(Offset.zero, picture);
|
||||
}
|
||||
|
||||
Picture _drawGreenRectIntoPicture() {
|
||||
final EnginePictureRecorder recorder = PictureRecorder() as EnginePictureRecorder;
|
||||
final RecordingCanvas canvas = recorder.beginRecording(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(20, 20, 50, 50),
|
||||
makePaint()..color = const Color(0xFF00FF00),
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
const String _base64Encoded20x20TestImage =
|
||||
'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAC4jAAAuIwF4pT92AAAA'
|
||||
'B3RJTUUH5AMFFBksg4i3gQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAj'
|
||||
'SURBVDjLY2TAC/7jlWVioACMah4ZmhnxpyHG0QAb1UyZZgBjWAIm/clP0AAAAABJRU5ErkJggg==';
|
||||
|
||||
HtmlImage _createRealTestImage() {
|
||||
return HtmlImage(
|
||||
createDomHTMLImageElement()..src = 'data:text/plain;base64,$_base64Encoded20x20TestImage',
|
||||
20,
|
||||
20,
|
||||
);
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 300, 300);
|
||||
|
||||
late BitmapCanvas canvas;
|
||||
late BitmapCanvas domCanvas;
|
||||
|
||||
setUp(() {
|
||||
canvas = BitmapCanvas(region, RenderStrategy());
|
||||
// setting isInsideSvgFilterTree true forces use of DOM canvas
|
||||
domCanvas = BitmapCanvas(region, RenderStrategy()..isInsideSvgFilterTree = true);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
canvas.rootElement.remove();
|
||||
domCanvas.rootElement.remove();
|
||||
});
|
||||
|
||||
test('draws lines with varying strokeWidth', () async {
|
||||
paintLines(canvas);
|
||||
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('canvas_lines_thickness.png', region: region);
|
||||
});
|
||||
test('draws lines with varying strokeWidth with dom canvas', () async {
|
||||
paintLines(domCanvas);
|
||||
|
||||
domDocument.body!.append(domCanvas.rootElement);
|
||||
await matchGoldenFile('canvas_lines_thickness_dom_canvas.png', region: region);
|
||||
});
|
||||
test('draws lines with negative Offset values with dom canvas', () async {
|
||||
// test rendering lines correctly with negative offset when using DOM
|
||||
final SurfacePaintData paintWithStyle =
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFFE91E63 // Colors.pink
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 16
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
// canvas.drawLine ignores paint.style (defaults to fill) according to api docs.
|
||||
// expect lines are rendered the same regardless of the set paint.style
|
||||
final SurfacePaintData paintWithoutStyle =
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFF4CAF50 // Colors.green
|
||||
..strokeWidth = 16
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
// test vertical, horizontal, and diagonal lines
|
||||
final List<Offset> points = <Offset>[
|
||||
const Offset(-25, 50),
|
||||
const Offset(45, 50),
|
||||
const Offset(100, -25),
|
||||
const Offset(100, 200),
|
||||
const Offset(-150, -145),
|
||||
const Offset(100, 200),
|
||||
];
|
||||
final List<Offset> shiftedPoints =
|
||||
points.map((Offset point) => point.translate(20, 20)).toList();
|
||||
|
||||
paintLinesFromPoints(domCanvas, paintWithStyle, points);
|
||||
paintLinesFromPoints(domCanvas, paintWithoutStyle, shiftedPoints);
|
||||
|
||||
domDocument.body!.append(domCanvas.rootElement);
|
||||
await matchGoldenFile('canvas_lines_with_negative_offset.png', region: region);
|
||||
});
|
||||
|
||||
test('drawLines method respects strokeCap with dom canvas', () async {
|
||||
final SurfacePaintData paintStrokeCapRound =
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFFE91E63 // Colors.pink
|
||||
..strokeWidth = 16
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
final SurfacePaintData paintStrokeCapSquare =
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFF4CAF50 // Colors.green
|
||||
..strokeWidth = 16
|
||||
..strokeCap = StrokeCap.square;
|
||||
|
||||
final SurfacePaintData paintStrokeCapButt =
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFFFF9800 // Colors.orange
|
||||
..strokeWidth = 16
|
||||
..strokeCap = StrokeCap.butt;
|
||||
|
||||
// test vertical, horizontal, and diagonal lines
|
||||
final List<Offset> points = <Offset>[
|
||||
const Offset(5, 50),
|
||||
const Offset(45, 50),
|
||||
const Offset(100, 5),
|
||||
const Offset(100, 200),
|
||||
const Offset(5, 10),
|
||||
const Offset(100, 200),
|
||||
];
|
||||
final List<Offset> shiftedPoints =
|
||||
points.map((Offset point) => point.translate(50, 50)).toList();
|
||||
final List<Offset> twiceShiftedPoints =
|
||||
shiftedPoints.map((Offset point) => point.translate(50, 50)).toList();
|
||||
|
||||
paintLinesFromPoints(domCanvas, paintStrokeCapRound, points);
|
||||
paintLinesFromPoints(domCanvas, paintStrokeCapSquare, shiftedPoints);
|
||||
paintLinesFromPoints(domCanvas, paintStrokeCapButt, twiceShiftedPoints);
|
||||
|
||||
domDocument.body!.append(domCanvas.rootElement);
|
||||
await matchGoldenFile('canvas_lines_with_strokeCap.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
void paintLines(BitmapCanvas canvas) {
|
||||
final SurfacePaintData nullPaint =
|
||||
SurfacePaintData()
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke;
|
||||
final SurfacePaintData paint1 =
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFF9E9E9E // Colors.grey
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke;
|
||||
final SurfacePaintData paint2 =
|
||||
SurfacePaintData()
|
||||
..color = 0x7fff0000
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke;
|
||||
final SurfacePaintData paint3 =
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFF4CAF50 //Colors.green
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke;
|
||||
// Draw markers around 100x100 box
|
||||
canvas.drawLine(const Offset(50, 40), const Offset(148, 40), nullPaint);
|
||||
canvas.drawLine(const Offset(50, 50), const Offset(52, 50), paint1);
|
||||
canvas.drawLine(const Offset(150, 50), const Offset(148, 50), paint1);
|
||||
canvas.drawLine(const Offset(50, 150), const Offset(52, 150), paint1);
|
||||
canvas.drawLine(const Offset(150, 150), const Offset(148, 150), paint1);
|
||||
// Draw diagonal
|
||||
canvas.drawLine(const Offset(50, 50), const Offset(150, 150), paint2);
|
||||
// Draw horizontal
|
||||
paint3.strokeWidth = 1.0;
|
||||
paint3.color = 0xFFFF0000;
|
||||
canvas.drawLine(const Offset(50, 55), const Offset(150, 55), paint3);
|
||||
paint3.strokeWidth = 2.0;
|
||||
paint3.color = 0xFF2196F3; // Colors.blue;
|
||||
canvas.drawLine(const Offset(50, 60), const Offset(150, 60), paint3);
|
||||
paint3.strokeWidth = 4.0;
|
||||
paint3.color = 0xFFFF9800; // Colors.orange;
|
||||
canvas.drawLine(const Offset(50, 70), const Offset(150, 70), paint3);
|
||||
}
|
||||
|
||||
void paintLinesFromPoints(BitmapCanvas canvas, SurfacePaintData paint, List<Offset> points) {
|
||||
// points list contains pairs of Offset points, so for loop step is 2
|
||||
for (int i = 0; i < points.length - 1; i += 2) {
|
||||
canvas.drawLine(points[i], points[i + 1], paint);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 150, 420);
|
||||
|
||||
late BitmapCanvas canvas;
|
||||
|
||||
setUp(() {
|
||||
canvas = BitmapCanvas(region, RenderStrategy());
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
canvas.rootElement.remove();
|
||||
});
|
||||
|
||||
test('draws rect with flipped coordinates L > R, T > B', () async {
|
||||
paintRects(canvas);
|
||||
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('canvas_rect_flipped.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
void paintRects(BitmapCanvas canvas) {
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(30, 40, 100, 50),
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFF4CAF50 //Colors.green
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
// swap left and right.
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(100, 150, 30, 140),
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFFF44336 //Colors.red
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
// Repeat above for fill
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(30, 240, 100, 250),
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFF4CAF50 //Colors.green
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
|
||||
// swap left and right.
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(100, 350, 30, 340),
|
||||
SurfacePaintData()
|
||||
..color =
|
||||
0xFFF44336 //Colors.red
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import '../screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
late RecordingCanvas rc;
|
||||
const Rect canvasRect = Rect.fromLTWH(0, 0, 500, 100);
|
||||
|
||||
const Rect region = Rect.fromLTWH(8, 8, 500, 100); // Compensate for old golden tester padding
|
||||
|
||||
final SurfacePaint niceRRectPaint =
|
||||
SurfacePaint()
|
||||
..color = const Color.fromRGBO(250, 186, 218, 1.0) // #fabada
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
// Some values to see how the algo behaves as radius get absurdly large
|
||||
const List<double> rRectRadii = <double>[0, 10, 20, 80, 8000];
|
||||
|
||||
const Radius someFixedRadius = Radius.circular(10);
|
||||
|
||||
setUp(() {
|
||||
rc = RecordingCanvas(const Rect.fromLTWH(0, 0, 500, 100));
|
||||
rc.translate(10, 10); // Center
|
||||
});
|
||||
|
||||
test('round square with big (equal) radius ends up as a circle', () async {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
rc.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(100 * i.toDouble(), 0, 80, 80),
|
||||
Radius.circular(rRectRadii[i]),
|
||||
),
|
||||
niceRRectPaint,
|
||||
);
|
||||
}
|
||||
await canvasScreenshot(rc, 'canvas_rrect_round_square', canvasRect: canvasRect, region: region);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/62631
|
||||
test('round square with flipped left/right coordinates', () async {
|
||||
rc.translate(35, 320);
|
||||
rc.drawRRect(
|
||||
RRect.fromRectAndRadius(const Rect.fromLTRB(-30, -100, 30, -300), const Radius.circular(30)),
|
||||
niceRRectPaint,
|
||||
);
|
||||
rc.drawPath(
|
||||
Path()
|
||||
..moveTo(0, 0)
|
||||
..lineTo(20, 0),
|
||||
niceRRectPaint,
|
||||
);
|
||||
await canvasScreenshot(
|
||||
rc,
|
||||
'canvas_rrect_flipped',
|
||||
canvasRect: canvasRect,
|
||||
region: const Rect.fromLTWH(0, 0, 100, 200),
|
||||
);
|
||||
});
|
||||
|
||||
test('round rect with big radius scale down smaller radius', () async {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
final Radius growingRadius = Radius.circular(rRectRadii[i]);
|
||||
final RRect rrect = RRect.fromRectAndCorners(
|
||||
Rect.fromLTWH(100 * i.toDouble(), 0, 80, 80),
|
||||
bottomRight: someFixedRadius,
|
||||
topRight: growingRadius,
|
||||
bottomLeft: growingRadius,
|
||||
);
|
||||
|
||||
rc.drawRRect(rrect, niceRRectPaint);
|
||||
}
|
||||
await canvasScreenshot(
|
||||
rc,
|
||||
'canvas_rrect_overlapping_radius',
|
||||
canvasRect: canvasRect,
|
||||
region: region,
|
||||
);
|
||||
});
|
||||
|
||||
test('diff round rect with big radius scale down smaller radius', () async {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
final Radius growingRadius = Radius.circular(rRectRadii[i]);
|
||||
final RRect outerRRect = RRect.fromRectAndCorners(
|
||||
Rect.fromLTWH(100 * i.toDouble(), 0, 80, 80),
|
||||
bottomRight: someFixedRadius,
|
||||
topRight: growingRadius,
|
||||
bottomLeft: growingRadius,
|
||||
);
|
||||
|
||||
// Inner is half of outer, but offset a little so it looks nicer
|
||||
final RRect innerRRect = RRect.fromRectAndCorners(
|
||||
Rect.fromLTWH(100 * i.toDouble() + 5, 5, 40, 40),
|
||||
bottomRight: someFixedRadius / 2,
|
||||
topRight: growingRadius / 2,
|
||||
bottomLeft: growingRadius / 2,
|
||||
);
|
||||
|
||||
rc.drawDRRect(outerRRect, innerRRect, niceRRectPaint);
|
||||
}
|
||||
|
||||
await canvasScreenshot(
|
||||
rc,
|
||||
'canvas_drrect_overlapping_radius',
|
||||
canvasRect: canvasRect,
|
||||
region: region,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 300, 300);
|
||||
|
||||
late BitmapCanvas canvas;
|
||||
|
||||
setUp(() {
|
||||
canvas = BitmapCanvas(region, RenderStrategy());
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
canvas.rootElement.remove();
|
||||
});
|
||||
|
||||
test('draws stroke joins', () async {
|
||||
paintStrokeJoins(canvas);
|
||||
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('canvas_stroke_joins.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
void paintStrokeJoins(BitmapCanvas canvas) {
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(0, 0, 300, 300),
|
||||
SurfacePaintData()
|
||||
..color = 0xFFFFFFFF
|
||||
..style = PaintingStyle.fill,
|
||||
); // white
|
||||
|
||||
Offset start = const Offset(20, 10);
|
||||
Offset mid = const Offset(120, 10);
|
||||
Offset end = const Offset(120, 20);
|
||||
|
||||
final List<StrokeCap> strokeCaps = <StrokeCap>[StrokeCap.butt, StrokeCap.round, StrokeCap.square];
|
||||
for (final StrokeCap cap in strokeCaps) {
|
||||
final List<StrokeJoin> joints = <StrokeJoin>[
|
||||
StrokeJoin.miter,
|
||||
StrokeJoin.bevel,
|
||||
StrokeJoin.round,
|
||||
];
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFFF44336),
|
||||
Color(0xFF4CAF50),
|
||||
Color(0xFF2196F3),
|
||||
]; // red, green, blue
|
||||
for (int i = 0; i < joints.length; i++) {
|
||||
final StrokeJoin join = joints[i];
|
||||
final Color color = colors[i % colors.length];
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(start.dx, start.dy);
|
||||
path.lineTo(mid.dx, mid.dy);
|
||||
path.lineTo(end.dx, end.dy);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4
|
||||
..color = color.value
|
||||
..strokeJoin = join
|
||||
..strokeCap = cap,
|
||||
);
|
||||
|
||||
start = start.translate(0, 20);
|
||||
mid = mid.translate(0, 20);
|
||||
end = end.translate(0, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// 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 'dart:math' as math;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 300, 300);
|
||||
|
||||
late BitmapCanvas canvas;
|
||||
|
||||
setUp(() {
|
||||
canvas = BitmapCanvas(region, RenderStrategy());
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
canvas.rootElement.remove();
|
||||
});
|
||||
|
||||
test('draws rects side by side with fill and stroke', () async {
|
||||
paintSideBySideRects(canvas);
|
||||
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('canvas_stroke_rects.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
void paintSideBySideRects(BitmapCanvas canvas) {
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(0, 0, 300, 300),
|
||||
SurfacePaintData()
|
||||
..color = 0xFFFFFFFF
|
||||
..style = PaintingStyle.fill,
|
||||
); // white
|
||||
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(0, 20, 40, 60),
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.fill
|
||||
..color = 0x7f0000ff,
|
||||
);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(40, 20, 80, 60),
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4
|
||||
..color = 0x7fff0000,
|
||||
);
|
||||
|
||||
// Rotate 30 degrees (in rad: deg*pi/180)
|
||||
canvas.transform(Matrix4.rotationZ(30.0 * math.pi / 180.0).storage);
|
||||
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(100, 60, 140, 100),
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.fill
|
||||
..color = 0x7fff00ff,
|
||||
);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(140, 60, 180, 100),
|
||||
SurfacePaintData()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4
|
||||
..color = 0x7fffff00,
|
||||
);
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(8, 8, 600, 800); // Compensate for old golden tester padding
|
||||
|
||||
Future<void> testPath(Path path, String goldenFileName) async {
|
||||
const Rect canvasBounds = Rect.fromLTWH(0, 0, 600, 800);
|
||||
final BitmapCanvas bitmapCanvas = BitmapCanvas(canvasBounds, RenderStrategy());
|
||||
final RecordingCanvas canvas = RecordingCanvas(canvasBounds);
|
||||
|
||||
SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..color = const Color(0x7F7F7F7F)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
|
||||
paint =
|
||||
SurfacePaint()
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF7F007F)
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
canvas.endRecording();
|
||||
|
||||
domDocument.body!.append(bitmapCanvas.rootElement);
|
||||
canvas.apply(bitmapCanvas, canvasBounds);
|
||||
await matchGoldenFile('$goldenFileName.png', region: region);
|
||||
bitmapCanvas.rootElement.remove();
|
||||
}
|
||||
|
||||
test('render conic with control point horizontal center', () async {
|
||||
const double yStart = 20;
|
||||
|
||||
const Offset p0 = Offset(25, yStart + 25);
|
||||
const Offset pc = Offset(60, yStart + 150);
|
||||
const Offset p2 = Offset(100, yStart + 50);
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(p0.dx, p0.dy);
|
||||
path.conicTo(pc.dx, pc.dy, p2.dx, p2.dy, 0.5);
|
||||
path.close();
|
||||
path.moveTo(p0.dx, p0.dy + 200);
|
||||
path.conicTo(pc.dx, pc.dy + 200, p2.dx, p2.dy + 200, 10);
|
||||
path.close();
|
||||
|
||||
await testPath(path, 'render_conic_1_w10');
|
||||
});
|
||||
|
||||
test('render conic with control point left of start point', () async {
|
||||
const double yStart = 20;
|
||||
|
||||
const Offset p0 = Offset(60, yStart + 25);
|
||||
const Offset pc = Offset(25, yStart + 150);
|
||||
const Offset p2 = Offset(100, yStart + 50);
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(p0.dx, p0.dy);
|
||||
path.conicTo(pc.dx, pc.dy, p2.dx, p2.dy, 0.5);
|
||||
path.close();
|
||||
path.moveTo(p0.dx, p0.dy + 200);
|
||||
path.conicTo(pc.dx, pc.dy + 200, p2.dx, p2.dy + 200, 10);
|
||||
path.close();
|
||||
|
||||
await testPath(path, 'render_conic_2_w10');
|
||||
});
|
||||
|
||||
test('render conic with control point above start point', () async {
|
||||
const double yStart = 20;
|
||||
|
||||
const Offset p0 = Offset(25, yStart + 125);
|
||||
const Offset pc = Offset(60, yStart + 50);
|
||||
const Offset p2 = Offset(100, yStart + 150);
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(p0.dx, p0.dy);
|
||||
path.conicTo(pc.dx, pc.dy, p2.dx, p2.dy, 0.5);
|
||||
path.close();
|
||||
path.moveTo(p0.dx, p0.dy + 200);
|
||||
path.conicTo(pc.dx, pc.dy + 200, p2.dx, p2.dy + 200, 10);
|
||||
path.close();
|
||||
|
||||
await testPath(path, 'render_conic_2');
|
||||
});
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
test('rect stroke with clip', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 250, 250);
|
||||
// Set `hasParagraphs` to true to force DOM rendering.
|
||||
final BitmapCanvas canvas = BitmapCanvas(region, RenderStrategy()..hasParagraphs = true);
|
||||
|
||||
const Rect rect = Rect.fromLTWH(0, 0, 150, 150);
|
||||
|
||||
canvas.clipRect(rect.inflate(10.0), ClipOp.intersect);
|
||||
|
||||
canvas.drawRect(
|
||||
rect,
|
||||
SurfacePaintData()
|
||||
..color = 0x6fff0000
|
||||
..strokeWidth = 20.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
canvas.drawRect(
|
||||
rect,
|
||||
SurfacePaintData()
|
||||
..color = 0x6f0000ff
|
||||
..strokeWidth = 10.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
canvas.drawRect(
|
||||
rect,
|
||||
SurfacePaintData()
|
||||
..color = 0xff000000
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
domDocument.body!.style.margin = '0px';
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('rect_clip_strokes_dom.png', region: region);
|
||||
canvas.rootElement.remove();
|
||||
});
|
||||
|
||||
test('rrect stroke with clip', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 250, 250);
|
||||
// Set `hasParagraphs` to true to force DOM rendering.
|
||||
final BitmapCanvas canvas = BitmapCanvas(region, RenderStrategy()..hasParagraphs = true);
|
||||
|
||||
final RRect rrect = RRect.fromRectAndRadius(
|
||||
const Rect.fromLTWH(0, 0, 150, 150),
|
||||
const Radius.circular(20),
|
||||
);
|
||||
|
||||
canvas.clipRect(rrect.outerRect.inflate(10.0), ClipOp.intersect);
|
||||
|
||||
canvas.drawRRect(
|
||||
rrect,
|
||||
SurfacePaintData()
|
||||
..color = 0x6fff0000
|
||||
..strokeWidth = 20.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
canvas.drawRRect(
|
||||
rrect,
|
||||
SurfacePaintData()
|
||||
..color = 0x6f0000ff
|
||||
..strokeWidth = 10.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
canvas.drawRRect(
|
||||
rrect,
|
||||
SurfacePaintData()
|
||||
..color = 0xff000000
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
domDocument.body!.style.margin = '0px';
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('rrect_clip_strokes_dom.png', region: region);
|
||||
canvas.rootElement.remove();
|
||||
});
|
||||
|
||||
test('circle stroke with clip', () async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 250, 250);
|
||||
// Set `hasParagraphs` to true to force DOM rendering.
|
||||
final BitmapCanvas canvas = BitmapCanvas(region, RenderStrategy()..hasParagraphs = true);
|
||||
|
||||
const Rect rect = Rect.fromLTWH(0, 0, 150, 150);
|
||||
|
||||
canvas.clipRect(rect.inflate(10.0), ClipOp.intersect);
|
||||
|
||||
canvas.drawCircle(
|
||||
rect.center,
|
||||
rect.width / 2,
|
||||
SurfacePaintData()
|
||||
..color = 0x6fff0000
|
||||
..strokeWidth = 20.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
canvas.drawCircle(
|
||||
rect.center,
|
||||
rect.width / 2,
|
||||
SurfacePaintData()
|
||||
..color = 0x6f0000ff
|
||||
..strokeWidth = 10.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
canvas.drawCircle(
|
||||
rect.center,
|
||||
rect.width / 2,
|
||||
SurfacePaintData()
|
||||
..color = 0xff000000
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
domDocument.body!.style.margin = '0px';
|
||||
domDocument.body!.append(canvas.rootElement);
|
||||
await matchGoldenFile('circle_clip_strokes_dom.png', region: region);
|
||||
canvas.rootElement.remove();
|
||||
});
|
||||
}
|
||||
@@ -1,461 +0,0 @@
|
||||
// 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 'dart:async';
|
||||
import 'dart:js_util' as js_util;
|
||||
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' hide ImageShader, TextStyle;
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const double screenWidth = 600.0;
|
||||
const double screenHeight = 800.0;
|
||||
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
setUp(() {
|
||||
GlContextCache.dispose();
|
||||
glRenderer = null;
|
||||
});
|
||||
|
||||
Future<void> testVertices(
|
||||
String fileName,
|
||||
Vertices vertices,
|
||||
BlendMode blendMode,
|
||||
Paint paint,
|
||||
) async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
rc.drawVertices(vertices as SurfaceVertices, blendMode, paint as SurfacePaint);
|
||||
await canvasScreenshot(rc, fileName, canvasRect: screenRect);
|
||||
}
|
||||
|
||||
test('Should draw green hairline triangles when colors array is null.', () async {
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangles,
|
||||
Float32List.fromList(<double>[
|
||||
20.0,
|
||||
20.0,
|
||||
220.0,
|
||||
10.0,
|
||||
110.0,
|
||||
220.0,
|
||||
220.0,
|
||||
320.0,
|
||||
20.0,
|
||||
310.0,
|
||||
200.0,
|
||||
420.0,
|
||||
]),
|
||||
);
|
||||
await testVertices(
|
||||
'draw_vertices_hairline_triangle',
|
||||
vertices,
|
||||
BlendMode.srcOver,
|
||||
Paint()..color = const Color.fromARGB(255, 0, 128, 0),
|
||||
);
|
||||
});
|
||||
|
||||
test('Should draw black hairline triangles when colors array is null'
|
||||
' and Paint() has no color.', () async {
|
||||
// ignore: unused_local_variable
|
||||
final Int32List colors = Int32List.fromList(<int>[
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
]);
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangles,
|
||||
Float32List.fromList(<double>[
|
||||
20.0,
|
||||
20.0,
|
||||
220.0,
|
||||
10.0,
|
||||
110.0,
|
||||
220.0,
|
||||
220.0,
|
||||
320.0,
|
||||
20.0,
|
||||
310.0,
|
||||
200.0,
|
||||
420.0,
|
||||
]),
|
||||
);
|
||||
await testVertices(
|
||||
'draw_vertices_hairline_triangle_black',
|
||||
vertices,
|
||||
BlendMode.srcOver,
|
||||
Paint(),
|
||||
);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/71442.
|
||||
test(
|
||||
'Should draw filled triangles when colors array is null'
|
||||
' and Paint() has color.',
|
||||
() async {
|
||||
// ignore: unused_local_variable
|
||||
final Int32List colors = Int32List.fromList(<int>[
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
]);
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangles,
|
||||
Float32List.fromList(<double>[
|
||||
20.0,
|
||||
20.0,
|
||||
220.0,
|
||||
10.0,
|
||||
110.0,
|
||||
220.0,
|
||||
220.0,
|
||||
320.0,
|
||||
20.0,
|
||||
310.0,
|
||||
200.0,
|
||||
420.0,
|
||||
]),
|
||||
);
|
||||
await testVertices(
|
||||
'draw_vertices_triangle_green_filled',
|
||||
vertices,
|
||||
BlendMode.srcOver,
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xFF00FF00),
|
||||
);
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
|
||||
test('Should draw hairline triangleFan.', () async {
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangleFan,
|
||||
Float32List.fromList(<double>[
|
||||
150.0,
|
||||
150.0,
|
||||
20.0,
|
||||
10.0,
|
||||
80.0,
|
||||
20.0,
|
||||
220.0,
|
||||
15.0,
|
||||
280.0,
|
||||
30.0,
|
||||
300.0,
|
||||
420.0,
|
||||
]),
|
||||
);
|
||||
|
||||
await testVertices(
|
||||
'draw_vertices_hairline_triangle_fan',
|
||||
vertices,
|
||||
BlendMode.srcOver,
|
||||
Paint()..color = const Color.fromARGB(255, 0, 128, 0),
|
||||
);
|
||||
});
|
||||
|
||||
test('Should draw hairline triangleStrip.', () async {
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangleStrip,
|
||||
Float32List.fromList(<double>[
|
||||
20.0,
|
||||
20.0,
|
||||
220.0,
|
||||
10.0,
|
||||
110.0,
|
||||
220.0,
|
||||
220.0,
|
||||
320.0,
|
||||
20.0,
|
||||
310.0,
|
||||
200.0,
|
||||
420.0,
|
||||
]),
|
||||
);
|
||||
await testVertices(
|
||||
'draw_vertices_hairline_triangle_strip',
|
||||
vertices,
|
||||
BlendMode.srcOver,
|
||||
Paint()..color = const Color.fromARGB(255, 0, 128, 0),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'Should draw triangles with colors.',
|
||||
() async {
|
||||
final Int32List colors = Int32List.fromList(<int>[
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
]);
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangles,
|
||||
Float32List.fromList(<double>[
|
||||
150.0,
|
||||
150.0,
|
||||
20.0,
|
||||
10.0,
|
||||
80.0,
|
||||
20.0,
|
||||
220.0,
|
||||
15.0,
|
||||
280.0,
|
||||
30.0,
|
||||
300.0,
|
||||
420.0,
|
||||
]),
|
||||
colors: colors,
|
||||
);
|
||||
|
||||
await testVertices(
|
||||
'draw_vertices_triangles',
|
||||
vertices,
|
||||
BlendMode.srcOver,
|
||||
Paint()..color = const Color.fromARGB(255, 0, 128, 0),
|
||||
);
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
|
||||
test(
|
||||
'Should draw triangles with colors and indices.',
|
||||
() async {
|
||||
final Int32List colors = Int32List.fromList(<int>[
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF0000FF,
|
||||
]);
|
||||
final Uint16List indices = Uint16List.fromList(<int>[0, 1, 2, 3, 4, 0]);
|
||||
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangles,
|
||||
Float32List.fromList(<double>[
|
||||
210.0,
|
||||
150.0,
|
||||
30.0,
|
||||
110.0,
|
||||
80.0,
|
||||
30.0,
|
||||
220.0,
|
||||
15.0,
|
||||
280.0,
|
||||
30.0,
|
||||
]),
|
||||
colors: colors,
|
||||
indices: indices,
|
||||
);
|
||||
|
||||
rc.drawVertices(vertices as SurfaceVertices, BlendMode.srcOver, SurfacePaint());
|
||||
|
||||
await canvasScreenshot(rc, 'draw_vertices_triangles_indexed', canvasRect: screenRect);
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
|
||||
test(
|
||||
'Should draw triangleFan with colors.',
|
||||
() async {
|
||||
final Int32List colors = Int32List.fromList(<int>[
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
]);
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangleFan,
|
||||
Float32List.fromList(<double>[
|
||||
150.0,
|
||||
150.0,
|
||||
20.0,
|
||||
10.0,
|
||||
80.0,
|
||||
20.0,
|
||||
220.0,
|
||||
15.0,
|
||||
280.0,
|
||||
30.0,
|
||||
300.0,
|
||||
420.0,
|
||||
]),
|
||||
colors: colors,
|
||||
);
|
||||
|
||||
await testVertices(
|
||||
'draw_vertices_triangle_fan',
|
||||
vertices,
|
||||
BlendMode.srcOver,
|
||||
Paint()..color = const Color.fromARGB(255, 0, 128, 0),
|
||||
);
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
|
||||
test(
|
||||
'Should draw triangleStrip with colors.',
|
||||
() async {
|
||||
final Int32List colors = Int32List.fromList(<int>[
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFFFF0000,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
]);
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangleStrip,
|
||||
Float32List.fromList(<double>[
|
||||
20.0,
|
||||
20.0,
|
||||
220.0,
|
||||
10.0,
|
||||
110.0,
|
||||
220.0,
|
||||
220.0,
|
||||
320.0,
|
||||
20.0,
|
||||
310.0,
|
||||
200.0,
|
||||
420.0,
|
||||
]),
|
||||
colors: colors,
|
||||
);
|
||||
await testVertices(
|
||||
'draw_vertices_triangle_strip',
|
||||
vertices,
|
||||
BlendMode.srcOver,
|
||||
Paint()..color = const Color.fromARGB(255, 0, 128, 0),
|
||||
);
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
|
||||
Future<void> testTexture(TileMode tileMode, String filename) async {
|
||||
final Uint16List indices = Uint16List.fromList(<int>[0, 1, 2, 3, 4, 0]);
|
||||
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
|
||||
final Vertices vertices = Vertices.raw(
|
||||
VertexMode.triangles,
|
||||
Float32List.fromList(<double>[210.0, 150.0, 0.0, 0.0, 80.0, 30.0, 220.0, 15.0, 280.0, 30.0]),
|
||||
indices: indices,
|
||||
);
|
||||
|
||||
final Float32List matrix4 = Matrix4.identity().storage;
|
||||
|
||||
final HtmlImage img = await createTestImage();
|
||||
final SurfacePaint paint = SurfacePaint();
|
||||
|
||||
final EngineImageShader imgShader = EngineImageShader(
|
||||
img,
|
||||
tileMode,
|
||||
tileMode,
|
||||
Float64List.fromList(matrix4),
|
||||
FilterQuality.high,
|
||||
);
|
||||
|
||||
paint.shader = imgShader;
|
||||
|
||||
rc.drawVertices(vertices as SurfaceVertices, BlendMode.srcOver, paint);
|
||||
await canvasScreenshot(rc, filename, canvasRect: screenRect);
|
||||
|
||||
expect(imgShader.debugDisposed, false);
|
||||
imgShader.dispose();
|
||||
expect(imgShader.debugDisposed, true);
|
||||
}
|
||||
|
||||
test(
|
||||
'Should draw triangle with texture and indices',
|
||||
() async {
|
||||
await testTexture(TileMode.clamp, 'draw_vertices_texture');
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
|
||||
test(
|
||||
'Should draw triangle with texture and indices',
|
||||
() async {
|
||||
await testTexture(TileMode.mirror, 'draw_vertices_texture_mirror');
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
|
||||
test(
|
||||
'Should draw triangle with texture and indices',
|
||||
() async {
|
||||
await testTexture(TileMode.repeated, 'draw_vertices_texture_repeated');
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
}
|
||||
|
||||
Future<HtmlImage> createTestImage({int width = 50, int height = 40}) {
|
||||
final DomCanvasElement canvas = createDomCanvasElement(width: width, height: height);
|
||||
final DomCanvasRenderingContext2D ctx = canvas.context2D;
|
||||
ctx.fillStyle = '#E04040';
|
||||
ctx.fillRect(0, 0, width / 3, height);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#40E080';
|
||||
ctx.fillRect(width / 3, 0, width / 3, height);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#2040E0';
|
||||
ctx.fillRect(2 * width / 3, 0, width / 3, height);
|
||||
ctx.fill();
|
||||
final DomHTMLImageElement imageElement = createDomHTMLImageElement();
|
||||
final Completer<HtmlImage> completer = Completer<HtmlImage>();
|
||||
imageElement.addEventListener(
|
||||
'load',
|
||||
createDomEventListener((DomEvent event) {
|
||||
completer.complete(HtmlImage(imageElement, width, height));
|
||||
}),
|
||||
);
|
||||
imageElement.src = js_util.callMethod<String>(canvas, 'toDataURL', <dynamic>[]);
|
||||
return completer.future;
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
// 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 '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' hide TextStyle;
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Matcher listEqual(List<int> source, {int tolerance = 0}) {
|
||||
return predicate((List<int> target) {
|
||||
if (source.length != target.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < source.length; i += 1) {
|
||||
if ((source[i] - target[i]).abs() > tolerance) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, source.toString());
|
||||
}
|
||||
|
||||
// Converts `rawPixels` into a list of bytes that represent raw pixels in rgba8888.
|
||||
//
|
||||
// Each element of `rawPixels` represents a bytes in order 0xRRGGBBAA, with
|
||||
// pixel order Left to right, then top to bottom.
|
||||
Uint8List _pixelsToBytes(List<int> rawPixels) {
|
||||
return Uint8List.fromList(<int>[
|
||||
for (final int pixel in rawPixels) ...<int>[
|
||||
(pixel >> 24) & 0xff, // r
|
||||
(pixel >> 16) & 0xff, // g
|
||||
(pixel >> 8) & 0xff, // b
|
||||
(pixel >> 0) & 0xff, // a
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
Future<Image> _encodeToHtmlThenDecode(
|
||||
Uint8List rawBytes,
|
||||
int width,
|
||||
int height, {
|
||||
PixelFormat pixelFormat = PixelFormat.rgba8888,
|
||||
}) async {
|
||||
final ImageDescriptor descriptor = ImageDescriptor.raw(
|
||||
await ImmutableBuffer.fromUint8List(rawBytes),
|
||||
width: width,
|
||||
height: height,
|
||||
pixelFormat: pixelFormat,
|
||||
);
|
||||
return (await (await descriptor.instantiateCodec()).getNextFrame()).image;
|
||||
}
|
||||
|
||||
// This utility function detects how the current Web engine decodes pixel data.
|
||||
//
|
||||
// The HTML renderer uses the BMP format to display pixel data, but it used to
|
||||
// use a wrong implementation. The bug has been fixed, but the fix breaks apps
|
||||
// that had to provide incorrect data to work around this issue. This function
|
||||
// is used in the migration guide to assist libraries that would like to run on
|
||||
// both pre- and post-patch engines by testing the current behavior on a single
|
||||
// pixel, making use the fact that the patch fixes the pixel order.
|
||||
//
|
||||
// The `format` argument is used for testing. In the actual code it should be
|
||||
// replaced by `PixelFormat.rgba8888`.
|
||||
//
|
||||
// See also:
|
||||
//
|
||||
// * Patch: https://github.com/flutter/engine/pull/29448
|
||||
// * Migration guide: https://docs.flutter.dev/release/breaking-changes/raw-images-on-web-uses-correct-origin-and-colors
|
||||
Future<bool> rawImageUsesCorrectBehavior(PixelFormat format) async {
|
||||
final ImageDescriptor descriptor = ImageDescriptor.raw(
|
||||
await ImmutableBuffer.fromUint8List(Uint8List.fromList(<int>[0xED, 0, 0, 0xFF])),
|
||||
width: 1,
|
||||
height: 1,
|
||||
pixelFormat: format,
|
||||
);
|
||||
final Image image = (await (await descriptor.instantiateCodec()).getNextFrame()).image;
|
||||
final Uint8List resultPixels = Uint8List.sublistView(
|
||||
(await image.toByteData(format: ImageByteFormat.rawStraightRgba))!,
|
||||
);
|
||||
return resultPixels[0] == 0xED;
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
test('Correctly encodes an opaque image', () async {
|
||||
// A 2x2 testing image without transparency.
|
||||
final Image sourceImage = await _encodeToHtmlThenDecode(
|
||||
_pixelsToBytes(<int>[0xFF0102FF, 0x04FE05FF, 0x0708FDFF, 0x0A0B0C00]),
|
||||
2,
|
||||
2,
|
||||
);
|
||||
final Uint8List actualPixels = Uint8List.sublistView(
|
||||
(await sourceImage.toByteData(format: ImageByteFormat.rawStraightRgba))!,
|
||||
);
|
||||
// The `benchmarkPixels` is identical to `sourceImage` except for the fully
|
||||
// transparent last pixel, whose channels are turned 0.
|
||||
final Uint8List benchmarkPixels = _pixelsToBytes(<int>[
|
||||
0xFF0102FF,
|
||||
0x04FE05FF,
|
||||
0x0708FDFF,
|
||||
0x00000000,
|
||||
]);
|
||||
expect(actualPixels, listEqual(benchmarkPixels));
|
||||
});
|
||||
|
||||
test('Correctly encodes an opaque image in bgra8888', () async {
|
||||
// A 2x2 testing image without transparency.
|
||||
final Image sourceImage = await _encodeToHtmlThenDecode(
|
||||
_pixelsToBytes(<int>[0xFF0102FF, 0x04FE05FF, 0x0708FDFF, 0x0A0B0C00]),
|
||||
2,
|
||||
2,
|
||||
pixelFormat: PixelFormat.bgra8888,
|
||||
);
|
||||
final Uint8List actualPixels = Uint8List.sublistView(
|
||||
(await sourceImage.toByteData(format: ImageByteFormat.rawStraightRgba))!,
|
||||
);
|
||||
// The `benchmarkPixels` is the same as `sourceImage` except that the R and
|
||||
// G channels are swapped and the fully transparent last pixel is turned 0.
|
||||
final Uint8List benchmarkPixels = _pixelsToBytes(<int>[
|
||||
0x0201FFFF,
|
||||
0x05FE04FF,
|
||||
0xFD0807FF,
|
||||
0x00000000,
|
||||
]);
|
||||
expect(actualPixels, listEqual(benchmarkPixels));
|
||||
});
|
||||
|
||||
test('Correctly encodes a transparent image', () async {
|
||||
// A 2x2 testing image with transparency.
|
||||
final Image sourceImage = await _encodeToHtmlThenDecode(
|
||||
_pixelsToBytes(<int>[0xFF800006, 0xFF800080, 0xFF8000C0, 0xFF8000FF]),
|
||||
2,
|
||||
2,
|
||||
);
|
||||
final Image blueBackground = await _encodeToHtmlThenDecode(
|
||||
_pixelsToBytes(<int>[0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF]),
|
||||
2,
|
||||
2,
|
||||
);
|
||||
// The standard way of testing the raw bytes of `sourceImage` is to draw
|
||||
// the image onto a canvas and fetch its data (see HtmlImage.toByteData).
|
||||
// But here, we draw an opaque background first before drawing the image,
|
||||
// and test if the blended result is expected.
|
||||
//
|
||||
// This is because, if we only draw the `sourceImage`, the resulting pixels
|
||||
// will be slightly off from the raw pixels. The reason is unknown, but
|
||||
// very likely because the canvas.getImageData introduces rounding errors
|
||||
// if any pixels are left semi-transparent, which might be caused by
|
||||
// converting to and from pre-multiplied values. See
|
||||
// https://github.com/flutter/flutter/issues/92958 .
|
||||
final DomCanvasElement canvas =
|
||||
createDomCanvasElement()
|
||||
..width = 2
|
||||
..height = 2;
|
||||
final DomCanvasRenderingContext2D ctx = canvas.context2D;
|
||||
ctx.drawImage((blueBackground as HtmlImage).imgElement, 0, 0);
|
||||
ctx.drawImage((sourceImage as HtmlImage).imgElement, 0, 0);
|
||||
|
||||
final DomImageData imageData = ctx.getImageData(0, 0, 2, 2);
|
||||
final List<int> actualPixels = imageData.data;
|
||||
|
||||
final Uint8List benchmarkPixels = _pixelsToBytes(<int>[
|
||||
0x0603F9FF,
|
||||
0x80407FFF,
|
||||
0xC0603FFF,
|
||||
0xFF8000FF,
|
||||
]);
|
||||
expect(actualPixels, listEqual(benchmarkPixels, tolerance: 1));
|
||||
});
|
||||
|
||||
test('The behavior detector works correctly', () async {
|
||||
expect(await rawImageUsesCorrectBehavior(PixelFormat.rgba8888), true);
|
||||
expect(await rawImageUsesCorrectBehavior(PixelFormat.bgra8888), false);
|
||||
});
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
// 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' hide TextStyle;
|
||||
|
||||
import '../common/matchers.dart';
|
||||
import '../common/test_initialization.dart';
|
||||
import 'screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const double screenWidth = 600.0;
|
||||
const double screenHeight = 800.0;
|
||||
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
const Color black12Color = Color(0x1F000000);
|
||||
const Color redAccentColor = Color(0xFFFF1744);
|
||||
const double kDashLength = 5.0;
|
||||
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
test('Should calculate tangent on line', () async {
|
||||
final Path path = Path();
|
||||
path.moveTo(50, 130);
|
||||
path.lineTo(150, 20);
|
||||
|
||||
final PathMetric metric = path.computeMetrics().first;
|
||||
final Tangent t = metric.getTangentForOffset(50.0)!;
|
||||
expect(t.position.dx, within(from: 83.633, distance: 0.01));
|
||||
expect(t.position.dy, within(from: 93.0, distance: 0.01));
|
||||
expect(t.vector.dx, within(from: 0.672, distance: 0.01));
|
||||
expect(t.vector.dy, within(from: -0.739, distance: 0.01));
|
||||
});
|
||||
|
||||
test('Should calculate tangent on cubic curve', () async {
|
||||
final Path path = Path();
|
||||
const double p1x = 240;
|
||||
const double p1y = 120;
|
||||
const double p2x = 320;
|
||||
const double p2y = 25;
|
||||
path.moveTo(150, 20);
|
||||
path.quadraticBezierTo(p1x, p1y, p2x, p2y);
|
||||
final PathMetric metric = path.computeMetrics().first;
|
||||
final Tangent t = metric.getTangentForOffset(50.0)!;
|
||||
expect(t.position.dx, within(from: 187.25, distance: 0.01));
|
||||
expect(t.position.dy, within(from: 53.33, distance: 0.01));
|
||||
expect(t.vector.dx, within(from: 0.82, distance: 0.01));
|
||||
expect(t.vector.dy, within(from: 0.56, distance: 0.01));
|
||||
});
|
||||
|
||||
test('Should calculate tangent on quadratic curve', () async {
|
||||
final Path path = Path();
|
||||
const double p0x = 150;
|
||||
const double p0y = 20;
|
||||
const double p1x = 320;
|
||||
const double p1y = 25;
|
||||
path.moveTo(150, 20);
|
||||
path.quadraticBezierTo(p0x, p0y, p1x, p1y);
|
||||
final PathMetric metric = path.computeMetrics().first;
|
||||
final Tangent t = metric.getTangentForOffset(50.0)!;
|
||||
expect(t.position.dx, within(from: 199.82, distance: 0.01));
|
||||
expect(t.position.dy, within(from: 21.46, distance: 0.01));
|
||||
expect(t.vector.dx, within(from: 0.99, distance: 0.01));
|
||||
expect(t.vector.dy, within(from: 0.02, distance: 0.01));
|
||||
});
|
||||
|
||||
// Test for extractPath to draw 5 pixel length dashed line using quad curve.
|
||||
test('Should draw dashed line on quadratic curve.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3
|
||||
..color = black12Color;
|
||||
final SurfacePaint redPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = redAccentColor;
|
||||
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(50, 130);
|
||||
path.lineTo(150, 20);
|
||||
const double p1x = 240;
|
||||
const double p1y = 120;
|
||||
const double p2x = 320;
|
||||
const double p2y = 25;
|
||||
path.quadraticBezierTo(p1x, p1y, p2x, p2y);
|
||||
|
||||
rc.drawPath(path, paint);
|
||||
|
||||
const double t0 = 0.2;
|
||||
const double t1 = 0.7;
|
||||
|
||||
final List<PathMetric> metrics = path.computeMetrics().toList();
|
||||
double totalLength = 0;
|
||||
for (final PathMetric m in metrics) {
|
||||
totalLength += m.length;
|
||||
}
|
||||
final Path dashedPath = Path();
|
||||
for (final PathMetric measurePath in path.computeMetrics()) {
|
||||
double distance = totalLength * t0;
|
||||
bool draw = true;
|
||||
while (distance < measurePath.length * t1) {
|
||||
const double length = kDashLength;
|
||||
if (draw) {
|
||||
dashedPath.addPath(measurePath.extractPath(distance, distance + length), Offset.zero);
|
||||
}
|
||||
distance += length;
|
||||
draw = !draw;
|
||||
}
|
||||
}
|
||||
rc.drawPath(dashedPath, redPaint);
|
||||
await canvasScreenshot(rc, 'path_dash_quadratic', canvasRect: screenRect);
|
||||
});
|
||||
|
||||
// Test for extractPath to draw 5 pixel length dashed line using cubic curve.
|
||||
test('Should draw dashed line on cubic curve.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3
|
||||
..color = black12Color;
|
||||
final SurfacePaint redPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = redAccentColor;
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(50, 130);
|
||||
path.lineTo(150, 20);
|
||||
const double p1x = 40;
|
||||
const double p1y = 120;
|
||||
const double p2x = 300;
|
||||
const double p2y = 130;
|
||||
const double p3x = 320;
|
||||
const double p3y = 25;
|
||||
path.cubicTo(p1x, p1y, p2x, p2y, p3x, p3y);
|
||||
|
||||
rc.drawPath(path, paint);
|
||||
|
||||
const double t0 = 0.2;
|
||||
const double t1 = 0.7;
|
||||
|
||||
final List<PathMetric> metrics = path.computeMetrics().toList();
|
||||
double totalLength = 0;
|
||||
for (final PathMetric m in metrics) {
|
||||
totalLength += m.length;
|
||||
}
|
||||
final Path dashedPath = Path();
|
||||
for (final PathMetric measurePath in path.computeMetrics()) {
|
||||
double distance = totalLength * t0;
|
||||
bool draw = true;
|
||||
while (distance < measurePath.length * t1) {
|
||||
const double length = kDashLength;
|
||||
if (draw) {
|
||||
dashedPath.addPath(measurePath.extractPath(distance, distance + length), Offset.zero);
|
||||
}
|
||||
distance += length;
|
||||
draw = !draw;
|
||||
}
|
||||
}
|
||||
rc.drawPath(dashedPath, redPaint);
|
||||
await canvasScreenshot(rc, 'path_dash_cubic', canvasRect: screenRect);
|
||||
});
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// 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';
|
||||
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/76885
|
||||
const bool issue76885Exists = true;
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
test('PathRef.getRRect with radius', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, const Radius.circular(2));
|
||||
path.addRRect(rrect);
|
||||
expect(path.toRoundedRect(), rrect);
|
||||
});
|
||||
|
||||
test('PathRef.getRRect with radius larger than rect', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, const Radius.circular(20));
|
||||
path.addRRect(rrect);
|
||||
expect(
|
||||
path.toRoundedRect(),
|
||||
// Path.addRRect will correct the radius to fit the dimensions, so when
|
||||
// extracting the rrect out of the path we don't get the original.
|
||||
RRect.fromLTRBR(0, 0, 10, 10, const Radius.circular(5)),
|
||||
);
|
||||
});
|
||||
|
||||
test('PathRef.getRRect with zero radius', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, Radius.zero);
|
||||
path.addRRect(rrect);
|
||||
expect(path.toRoundedRect(), isNull);
|
||||
expect(path.toRect(), rrect.outerRect);
|
||||
});
|
||||
|
||||
test('PathRef.getRRect elliptical', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, const Radius.elliptical(2, 4));
|
||||
path.addRRect(rrect);
|
||||
expect(path.toRoundedRect(), rrect);
|
||||
});
|
||||
|
||||
test('PathRef.getRRect elliptical zero x', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, const Radius.elliptical(0, 3));
|
||||
path.addRRect(rrect);
|
||||
expect(path.toRoundedRect(), isNull);
|
||||
expect(path.toRect(), rrect.outerRect);
|
||||
});
|
||||
|
||||
test('PathRef.getRRect elliptical zero y', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
final RRect rrect = RRect.fromLTRBR(0, 0, 10, 10, const Radius.elliptical(3, 0));
|
||||
path.addRRect(rrect);
|
||||
expect(path.toRoundedRect(), isNull);
|
||||
expect(path.toRect(), rrect.outerRect);
|
||||
});
|
||||
|
||||
test('PathRef.getRect returns a Rect from a valid Path and null otherwise', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
// Draw a line
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(10, 0);
|
||||
expect(path.pathRef.getRect(), isNull);
|
||||
// Draw two other lines to get a valid rectangle
|
||||
path.lineTo(10, 10);
|
||||
path.lineTo(0, 10);
|
||||
expect(path.pathRef.getRect(), const Rect.fromLTWH(0, 0, 10, 10));
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/111750
|
||||
test('PathRef.getRect returns Rect with positive width and height', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
// Draw a rectangle starting from bottom right corner
|
||||
path.moveTo(10, 10);
|
||||
path.lineTo(0, 10);
|
||||
path.lineTo(0, 0);
|
||||
path.lineTo(10, 0);
|
||||
// pathRef.getRect() should return a rectangle with positive height and width
|
||||
expect(path.pathRef.getRect(), const Rect.fromLTWH(0, 0, 10, 10));
|
||||
});
|
||||
|
||||
// This test demonstrates the issue with attempting to reconstruct an RRect
|
||||
// with imprecision introduced by comparing double values. We should fix this
|
||||
// by removing the need to reconstruct rrects:
|
||||
// https://github.com/flutter/flutter/issues/76885
|
||||
test('PathRef.getRRect with nearly zero corner', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
final RRect original = RRect.fromLTRBR(0, 0, 10, 10, const Radius.elliptical(0.00000001, 5));
|
||||
path.addRRect(original);
|
||||
expect(path.toRoundedRect(), original);
|
||||
}, skip: issue76885Exists);
|
||||
}
|
||||
@@ -1,596 +0,0 @@
|
||||
// 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 'dart:js_util' as js_util;
|
||||
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' hide window;
|
||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
|
||||
import '../common/matchers.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
group('Path', () {
|
||||
test('Should have no subpaths when created', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
expect(path.isEmpty, isTrue);
|
||||
});
|
||||
|
||||
test('LineTo should add command', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(5.0, 10.0);
|
||||
path.lineTo(20.0, 40.0);
|
||||
path.lineTo(30.0, 50.0);
|
||||
expect(path.pathRef.countPoints(), 3);
|
||||
expect(path.pathRef.atPoint(2).dx, 30.0);
|
||||
expect(path.pathRef.atPoint(2).dy, 50.0);
|
||||
});
|
||||
|
||||
test('LineTo should add moveTo 0,0 when first call to Path API', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.lineTo(20.0, 40.0);
|
||||
expect(path.pathRef.countPoints(), 2);
|
||||
expect(path.pathRef.atPoint(0).dx, 0);
|
||||
expect(path.pathRef.atPoint(0).dy, 0);
|
||||
expect(path.pathRef.atPoint(1).dx, 20.0);
|
||||
expect(path.pathRef.atPoint(1).dy, 40.0);
|
||||
});
|
||||
|
||||
test('relativeLineTo should increments currentX', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(5.0, 10.0);
|
||||
path.lineTo(20.0, 40.0);
|
||||
path.relativeLineTo(5.0, 5.0);
|
||||
expect(path.pathRef.countPoints(), 3);
|
||||
expect(path.pathRef.atPoint(2).dx, 25.0);
|
||||
expect(path.pathRef.atPoint(2).dy, 45.0);
|
||||
});
|
||||
|
||||
test('Should allow calling relativeLineTo before moveTo', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.relativeLineTo(5.0, 5.0);
|
||||
path.moveTo(5.0, 10.0);
|
||||
expect(path.pathRef.countPoints(), 3);
|
||||
expect(path.pathRef.atPoint(1).dx, 5.0);
|
||||
expect(path.pathRef.atPoint(1).dy, 5.0);
|
||||
expect(path.pathRef.atPoint(2).dx, 5.0);
|
||||
expect(path.pathRef.atPoint(2).dy, 10.0);
|
||||
});
|
||||
|
||||
test('Should allow relativeLineTo after reset', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
final Path subPath = Path();
|
||||
subPath.moveTo(50.0, 60.0);
|
||||
subPath.lineTo(200.0, 200.0);
|
||||
path.extendWithPath(subPath, Offset.zero);
|
||||
path.reset();
|
||||
path.relativeLineTo(5.0, 5.0);
|
||||
expect(path.pathRef.countPoints(), 2);
|
||||
expect(path.pathRef.atPoint(0).dx, 0);
|
||||
expect(path.pathRef.atPoint(0).dy, 0);
|
||||
expect(path.pathRef.atPoint(1).dx, 5.0);
|
||||
});
|
||||
|
||||
test('Should detect rectangular path', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.addRect(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0));
|
||||
expect(path.toRect(), const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0));
|
||||
});
|
||||
|
||||
test('Should detect horizontal line path', () {
|
||||
SurfacePath path = SurfacePath();
|
||||
path.moveTo(10, 20);
|
||||
path.lineTo(100, 0);
|
||||
expect(path.toStraightLine(), null);
|
||||
path = SurfacePath();
|
||||
path.moveTo(10, 20);
|
||||
path.lineTo(200, 20);
|
||||
final Rect r = path.toStraightLine()!;
|
||||
expect(r, equals(const Rect.fromLTRB(10, 20, 200, 20)));
|
||||
});
|
||||
|
||||
test('Should detect vertical line path', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(10, 20);
|
||||
path.lineTo(10, 200);
|
||||
final Rect r = path.toStraightLine()!;
|
||||
expect(r, equals(const Rect.fromLTRB(10, 20, 10, 200)));
|
||||
});
|
||||
|
||||
test('Should detect non rectangular path if empty', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
expect(path.toRect(), null);
|
||||
});
|
||||
|
||||
test('Should detect non rectangular path if there are multiple subpaths', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.addRect(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0));
|
||||
path.addRect(const Rect.fromLTWH(5.0, 6.0, 7.0, 8.0));
|
||||
expect(path.toRect(), null);
|
||||
});
|
||||
|
||||
test('Should detect rounded rectangular path', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.addRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(1.0, 2.0, 30.0, 40.0),
|
||||
const Radius.circular(2.0),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
path.toRoundedRect(),
|
||||
RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(1.0, 2.0, 30.0, 40.0),
|
||||
const Radius.circular(2.0),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('Should detect non rounded rectangular path if empty', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
expect(path.toRoundedRect(), null);
|
||||
});
|
||||
|
||||
test('Should detect rectangular path is not round', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.addRect(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0));
|
||||
expect(path.toRoundedRect(), null);
|
||||
});
|
||||
|
||||
test('Should detect non rounded rectangular path if there are '
|
||||
'multiple subpaths', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.addRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0),
|
||||
const Radius.circular(2.0),
|
||||
),
|
||||
);
|
||||
path.addRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0),
|
||||
const Radius.circular(2.0),
|
||||
),
|
||||
);
|
||||
expect(path.toRoundedRect(), null);
|
||||
});
|
||||
|
||||
test('Should compute bounds as empty for empty and moveTo only path', () {
|
||||
final Path emptyPath = Path();
|
||||
expect(emptyPath.getBounds(), Rect.zero);
|
||||
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(50, 60);
|
||||
expect(path.getBounds(), const Rect.fromLTRB(50, 60, 50, 60));
|
||||
});
|
||||
|
||||
test('Should compute bounds for multiple addRect calls', () {
|
||||
final Path emptyPath = Path();
|
||||
expect(emptyPath.getBounds(), Rect.zero);
|
||||
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.addRect(const Rect.fromLTWH(0, 0, 270, 45));
|
||||
path.addRect(const Rect.fromLTWH(134.5, 0, 1, 45));
|
||||
expect(path.getBounds(), const Rect.fromLTRB(0, 0, 270, 45));
|
||||
});
|
||||
|
||||
test('Should compute bounds for addRRect', () {
|
||||
SurfacePath path = SurfacePath();
|
||||
const Rect bounds = Rect.fromLTRB(30, 40, 400, 300);
|
||||
RRect rrect = RRect.fromRectAndCorners(
|
||||
bounds,
|
||||
topLeft: const Radius.elliptical(1, 2),
|
||||
topRight: const Radius.elliptical(3, 4),
|
||||
bottomLeft: const Radius.elliptical(5, 6),
|
||||
bottomRight: const Radius.elliptical(7, 8),
|
||||
);
|
||||
path.addRRect(rrect);
|
||||
expect(path.getBounds(), bounds);
|
||||
expect(path.toRoundedRect(), rrect);
|
||||
path = SurfacePath();
|
||||
rrect = RRect.fromRectAndCorners(
|
||||
bounds,
|
||||
topLeft: const Radius.elliptical(0, 2),
|
||||
topRight: const Radius.elliptical(3, 4),
|
||||
bottomLeft: const Radius.elliptical(5, 6),
|
||||
bottomRight: const Radius.elliptical(7, 8),
|
||||
);
|
||||
path.addRRect(rrect);
|
||||
expect(path.getBounds(), bounds);
|
||||
expect(path.toRoundedRect(), rrect);
|
||||
path = SurfacePath();
|
||||
rrect = RRect.fromRectAndCorners(
|
||||
bounds,
|
||||
topRight: const Radius.elliptical(3, 4),
|
||||
bottomLeft: const Radius.elliptical(5, 6),
|
||||
bottomRight: const Radius.elliptical(7, 8),
|
||||
);
|
||||
path.addRRect(rrect);
|
||||
expect(path.getBounds(), bounds);
|
||||
expect(path.toRoundedRect(), rrect);
|
||||
path = SurfacePath();
|
||||
rrect = RRect.fromRectAndCorners(
|
||||
bounds,
|
||||
topLeft: const Radius.elliptical(1, 2),
|
||||
bottomLeft: const Radius.elliptical(5, 6),
|
||||
bottomRight: const Radius.elliptical(7, 8),
|
||||
);
|
||||
path.addRRect(rrect);
|
||||
expect(path.getBounds(), bounds);
|
||||
expect(path.toRoundedRect(), rrect);
|
||||
path = SurfacePath();
|
||||
rrect = RRect.fromRectAndCorners(
|
||||
bounds,
|
||||
topLeft: const Radius.elliptical(1, 2),
|
||||
topRight: const Radius.elliptical(3, 4),
|
||||
bottomRight: const Radius.elliptical(7, 8),
|
||||
);
|
||||
path.addRRect(rrect);
|
||||
expect(path.getBounds(), bounds);
|
||||
expect(path.toRoundedRect(), rrect);
|
||||
path = SurfacePath();
|
||||
rrect = RRect.fromRectAndCorners(
|
||||
bounds,
|
||||
topLeft: const Radius.elliptical(1, 2),
|
||||
topRight: const Radius.elliptical(3, 4),
|
||||
bottomLeft: const Radius.elliptical(5, 6),
|
||||
);
|
||||
path.addRRect(rrect);
|
||||
expect(path.getBounds(), bounds);
|
||||
expect(path.toRoundedRect(), rrect);
|
||||
});
|
||||
|
||||
test('Should compute bounds for lines', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(25, 30);
|
||||
path.lineTo(100, 200);
|
||||
expect(path.getBounds(), const Rect.fromLTRB(25, 30, 100, 200));
|
||||
|
||||
final SurfacePath path2 = SurfacePath();
|
||||
path2.moveTo(250, 300);
|
||||
path2.lineTo(50, 60);
|
||||
expect(path2.getBounds(), const Rect.fromLTRB(50, 60, 250, 300));
|
||||
});
|
||||
|
||||
test('Should compute bounds for polygon', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.addPolygon(const <Offset>[
|
||||
Offset(50, 100),
|
||||
Offset(250, 100),
|
||||
Offset(152, 180),
|
||||
Offset(159, 200),
|
||||
Offset(151, 190),
|
||||
], true);
|
||||
expect(path.getBounds(), const Rect.fromLTRB(50, 100, 250, 200));
|
||||
});
|
||||
|
||||
test('Should compute bounds for quadraticBezierTo', () {
|
||||
final SurfacePath path1 = SurfacePath();
|
||||
path1.moveTo(285.2, 682.1);
|
||||
path1.quadraticBezierTo(432.0, 431.4, 594.9, 681.2);
|
||||
expect(
|
||||
path1.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(285.2, 556.5, 594.9, 682.1)),
|
||||
);
|
||||
|
||||
// Control point below start , end.
|
||||
final SurfacePath path2 = SurfacePath();
|
||||
path2.moveTo(285.2, 682.1);
|
||||
path2.quadraticBezierTo(447.4, 946.8, 594.9, 681.2);
|
||||
expect(
|
||||
path2.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(285.2, 681.2, 594.9, 814.2)),
|
||||
);
|
||||
|
||||
// Control point to the right of end point.
|
||||
final SurfacePath path3 = SurfacePath();
|
||||
path3.moveTo(468.3, 685.6);
|
||||
path3.quadraticBezierTo(644.7, 555.2, 594.9, 681.2);
|
||||
expect(
|
||||
path3.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(468.3, 619.3, 605.9, 685.6)),
|
||||
);
|
||||
});
|
||||
|
||||
test('Should compute bounds for cubicTo', () {
|
||||
final SurfacePath path1 = SurfacePath();
|
||||
path1.moveTo(220, 300);
|
||||
path1.cubicTo(230, 120, 400, 125, 410, 280);
|
||||
expect(
|
||||
path1.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(220.0, 164.3, 410.0, 300.0)),
|
||||
);
|
||||
|
||||
// control point 1 to the right of control point 2
|
||||
final SurfacePath path2 = SurfacePath();
|
||||
path2.moveTo(220, 300);
|
||||
path2.cubicTo(564.2, 13.7, 400.0, 125.0, 410.0, 280.0);
|
||||
expect(
|
||||
path2.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(220.0, 122.8, 440.5, 300.0)),
|
||||
);
|
||||
|
||||
// control point 1 to the right of control point 2 inflection
|
||||
final SurfacePath path3 = SurfacePath();
|
||||
path3.moveTo(220, 300);
|
||||
path3.cubicTo(839.8, 67.9, 400.0, 125.0, 410.0, 280.0);
|
||||
expect(
|
||||
path3.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(220.0, 144.5, 552.1, 300.0)),
|
||||
);
|
||||
|
||||
// control point 1 below and between start and end points
|
||||
final SurfacePath path4 = SurfacePath();
|
||||
path4.moveTo(220.0, 300.0);
|
||||
path4.cubicTo(354.8, 388.3, 400.0, 125.0, 410.0, 280.0);
|
||||
expect(
|
||||
path4.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(220.0, 230.0, 410.0, 318.6)),
|
||||
);
|
||||
|
||||
// control points inverted below
|
||||
final SurfacePath path5 = SurfacePath();
|
||||
path5.moveTo(220.0, 300.0);
|
||||
path5.cubicTo(366.5, 487.3, 256.4, 489.9, 410.0, 280.0);
|
||||
expect(
|
||||
path5.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(220.0, 280.0, 410.0, 439.0)),
|
||||
);
|
||||
|
||||
// control points inverted below wide
|
||||
final SurfacePath path6 = SurfacePath();
|
||||
path6.moveTo(220.0, 300.0);
|
||||
path6.cubicTo(496.1, 485.5, 121.4, 491.6, 410.0, 280.0);
|
||||
expect(
|
||||
path6.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(220.0, 280.0, 410.0, 439.0)),
|
||||
);
|
||||
|
||||
// control point 2 and end point swapped
|
||||
final SurfacePath path7 = SurfacePath();
|
||||
path7.moveTo(220.0, 300.0);
|
||||
path7.cubicTo(230.0, 120.0, 394.5, 296.1, 382.3, 124.1);
|
||||
expect(
|
||||
path7.getBounds(),
|
||||
within<Rect>(distance: 0.1, from: const Rect.fromLTRB(220.0, 124.1, 382.9, 300.0)),
|
||||
);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/46813.
|
||||
test('Should deep copy path', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(25, 30);
|
||||
path.lineTo(100, 200);
|
||||
expect(path.getBounds(), const Rect.fromLTRB(25, 30, 100, 200));
|
||||
|
||||
final SurfacePath path2 = SurfacePath.from(path);
|
||||
path2.lineTo(250, 300);
|
||||
expect(path2.getBounds(), const Rect.fromLTRB(25, 30, 250, 300));
|
||||
// Expect original path to stay the same.
|
||||
expect(path.getBounds(), const Rect.fromLTRB(25, 30, 100, 200));
|
||||
});
|
||||
|
||||
test('Should handle contains inclusive right,bottom coordinates', () {
|
||||
final Path path = Path();
|
||||
path.moveTo(50, 60);
|
||||
path.lineTo(110, 60);
|
||||
path.lineTo(110, 190);
|
||||
path.lineTo(50, 190);
|
||||
path.close();
|
||||
expect(path.contains(const Offset(80, 190)), isTrue);
|
||||
expect(path.contains(const Offset(110, 80)), isTrue);
|
||||
expect(path.contains(const Offset(110, 190)), isTrue);
|
||||
expect(path.contains(const Offset(110, 191)), isFalse);
|
||||
});
|
||||
|
||||
test('Should not contain top-left of beveled border', () {
|
||||
final Path path = Path();
|
||||
path.moveTo(10, 25);
|
||||
path.lineTo(15, 20);
|
||||
path.lineTo(25, 20);
|
||||
path.lineTo(30, 25);
|
||||
path.lineTo(30, 35);
|
||||
path.lineTo(25, 40);
|
||||
path.lineTo(15, 40);
|
||||
path.lineTo(10, 35);
|
||||
path.close();
|
||||
expect(path.contains(const Offset(10, 20)), isFalse);
|
||||
});
|
||||
|
||||
test('Computes contains for cubic curves', () {
|
||||
final Path path = Path();
|
||||
path.moveTo(10, 25);
|
||||
path.cubicTo(10, 20, 10, 20, 20, 15);
|
||||
path.lineTo(25, 20);
|
||||
path.cubicTo(30, 20, 30, 20, 30, 25);
|
||||
path.lineTo(30, 35);
|
||||
path.cubicTo(30, 40, 30, 40, 25, 40);
|
||||
path.lineTo(15, 40);
|
||||
path.cubicTo(10, 40, 10, 40, 10, 35);
|
||||
path.close();
|
||||
expect(path.contains(const Offset(10, 20)), isFalse);
|
||||
expect(path.contains(const Offset(30, 40)), isFalse);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/44470
|
||||
test(
|
||||
'Should handle contains for devicepixelratio != 1.0',
|
||||
() {
|
||||
js_util.setProperty(domWindow, 'devicePixelRatio', 4.0);
|
||||
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(4.0);
|
||||
final Path path =
|
||||
Path()
|
||||
..moveTo(50, 0)
|
||||
..lineTo(100, 100)
|
||||
..lineTo(0, 100)
|
||||
..lineTo(50, 0)
|
||||
..close();
|
||||
expect(path.contains(const Offset(50, 50)), isTrue);
|
||||
js_util.setProperty(domWindow, 'devicePixelRatio', 1.0);
|
||||
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(1.0);
|
||||
// TODO(ferhat): Investigate failure on CI. Locally this passes.
|
||||
// [Exception... "Failure" nsresult: "0x80004005 (NS_ERROR_FAILURE)"
|
||||
},
|
||||
skip: ui_web.browser.browserEngine == ui_web.BrowserEngine.firefox,
|
||||
);
|
||||
|
||||
// Path contains should handle case where invalid RRect with large
|
||||
// corner radius is used for hit test. Use case is a RenderPhysicalShape
|
||||
// with a clipper that contains RRect of width/height 50 but corner radius
|
||||
// of 100.
|
||||
//
|
||||
// Regression test for https://github.com/flutter/flutter/issues/48887
|
||||
test('Should hit test correctly for malformed rrect', () {
|
||||
// Correctly formed rrect.
|
||||
final Path path1 =
|
||||
Path()..addRRect(RRect.fromLTRBR(50, 50, 100, 100, const Radius.circular(20)));
|
||||
expect(path1.contains(const Offset(75, 75)), isTrue);
|
||||
expect(path1.contains(const Offset(52, 75)), isTrue);
|
||||
expect(path1.contains(const Offset(50, 50)), isFalse);
|
||||
expect(path1.contains(const Offset(100, 50)), isFalse);
|
||||
expect(path1.contains(const Offset(100, 100)), isFalse);
|
||||
expect(path1.contains(const Offset(50, 100)), isFalse);
|
||||
|
||||
final Path path2 =
|
||||
Path()..addRRect(RRect.fromLTRBR(50, 50, 100, 100, const Radius.circular(100)));
|
||||
expect(path2.contains(const Offset(75, 75)), isTrue);
|
||||
expect(path2.contains(const Offset(52, 75)), isTrue);
|
||||
expect(path2.contains(const Offset(50, 50)), isFalse);
|
||||
expect(path2.contains(const Offset(100, 50)), isFalse);
|
||||
expect(path2.contains(const Offset(100, 100)), isFalse);
|
||||
expect(path2.contains(const Offset(50, 100)), isFalse);
|
||||
});
|
||||
|
||||
test('Should set segment masks', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.pathRef.computeSegmentMask();
|
||||
expect(path.pathRef.segmentMasks, 0);
|
||||
path.moveTo(20, 40);
|
||||
path.pathRef.computeSegmentMask();
|
||||
expect(path.pathRef.segmentMasks, 0);
|
||||
path.lineTo(200, 40);
|
||||
path.pathRef.computeSegmentMask();
|
||||
expect(path.pathRef.segmentMasks, SPathSegmentMask.kLine_SkPathSegmentMask);
|
||||
});
|
||||
|
||||
test('Should convert conic to quad when approximation error is small', () {
|
||||
final Conic conic = Conic(
|
||||
120.0,
|
||||
20.0,
|
||||
160.99470420829266,
|
||||
20.0,
|
||||
190.19301120261332,
|
||||
34.38770865870253,
|
||||
0.9252691032413082,
|
||||
);
|
||||
expect(conic.toQuads().length, 3);
|
||||
});
|
||||
|
||||
test('Should be able to construct from empty path', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
expect(path.isEmpty, isTrue);
|
||||
final SurfacePath path2 = SurfacePath.from(path);
|
||||
expect(path2.isEmpty, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('PathRef', () {
|
||||
test('Should return empty when created', () {
|
||||
final PathRef pathRef = PathRef();
|
||||
expect(pathRef.isEmpty, isTrue);
|
||||
});
|
||||
|
||||
test('Should return non-empty when mutated', () {
|
||||
final PathRef pathRef = PathRef();
|
||||
pathRef.growForVerb(SPath.kMoveVerb, 0);
|
||||
expect(pathRef.isEmpty, isFalse);
|
||||
});
|
||||
});
|
||||
group('PathRefIterator', () {
|
||||
test('Should iterate through empty path', () {
|
||||
final Float32List points = Float32List(20);
|
||||
final PathRef pathRef = PathRef();
|
||||
final PathRefIterator iter = PathRefIterator(pathRef);
|
||||
expect(iter.next(points), SPath.kDoneVerb);
|
||||
});
|
||||
|
||||
test('Should iterate through verbs', () {
|
||||
final Float32List points = Float32List(20);
|
||||
final PathRef pathRef = PathRef();
|
||||
pathRef.growForVerb(SPath.kMoveVerb, 0);
|
||||
pathRef.growForVerb(SPath.kLineVerb, 0);
|
||||
pathRef.growForVerb(SPath.kQuadVerb, 0);
|
||||
pathRef.growForVerb(SPath.kCubicVerb, 0);
|
||||
pathRef.growForVerb(SPath.kConicVerb, 0.8);
|
||||
pathRef.growForVerb(SPath.kLineVerb, 0.8);
|
||||
final PathRefIterator iter = PathRefIterator(pathRef);
|
||||
expect(iter.next(points), SPath.kMoveVerb);
|
||||
expect(iter.next(points), SPath.kLineVerb);
|
||||
expect(iter.next(points), SPath.kQuadVerb);
|
||||
expect(iter.next(points), SPath.kCubicVerb);
|
||||
expect(iter.next(points), SPath.kConicVerb);
|
||||
expect(iter.next(points), SPath.kLineVerb);
|
||||
expect(iter.next(points), SPath.kDoneVerb);
|
||||
});
|
||||
|
||||
test('Should iterate by index through empty path', () {
|
||||
final PathRef pathRef = PathRef();
|
||||
final PathRefIterator iter = PathRefIterator(pathRef);
|
||||
expect(iter.nextIndex(), SPath.kDoneVerb);
|
||||
});
|
||||
|
||||
test('Should iterate through contours', () {
|
||||
final PathRef pathRef = PathRef();
|
||||
pathRef.growForVerb(SPath.kMoveVerb, 0);
|
||||
pathRef.growForVerb(SPath.kLineVerb, 0);
|
||||
pathRef.growForVerb(SPath.kQuadVerb, 0);
|
||||
pathRef.growForVerb(SPath.kCubicVerb, 0);
|
||||
pathRef.growForVerb(SPath.kMoveVerb, 0);
|
||||
pathRef.growForVerb(SPath.kConicVerb, 0.8);
|
||||
pathRef.growForVerb(SPath.kLineVerb, 0.8);
|
||||
pathRef.growForVerb(SPath.kCloseVerb, 0.8);
|
||||
pathRef.growForVerb(SPath.kMoveVerb, 0);
|
||||
pathRef.growForVerb(SPath.kLineVerb, 0);
|
||||
pathRef.growForVerb(SPath.kLineVerb, 0);
|
||||
final PathRefIterator iter = PathRefIterator(pathRef);
|
||||
int start = iter.pointIndex;
|
||||
int end = iter.skipToNextContour();
|
||||
expect(end - start, 7);
|
||||
|
||||
start = end;
|
||||
end = iter.skipToNextContour();
|
||||
expect(end - start, 4);
|
||||
|
||||
start = end;
|
||||
end = iter.skipToNextContour();
|
||||
expect(end - start, 3);
|
||||
|
||||
start = end;
|
||||
end = iter.skipToNextContour();
|
||||
expect(start, end);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/68702.
|
||||
test('Path should return correct bounds after transform', () {
|
||||
final Path path1 =
|
||||
Path()
|
||||
..moveTo(100, 100)
|
||||
..lineTo(200, 100)
|
||||
..lineTo(150, 200)
|
||||
..close();
|
||||
final SurfacePath path2 = Path.from(path1) as SurfacePath;
|
||||
final Rect bounds = path2.pathRef.getBounds();
|
||||
final SurfacePath transformedPath = path2.transform(
|
||||
Matrix4.identity().scaled(0.5, 0.5).toFloat64(),
|
||||
);
|
||||
expect(transformedPath.pathRef.getBounds(), isNot(bounds));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
enum PaintMode { kStrokeAndFill, kStroke, kFill, kStrokeWidthOnly }
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(8, 8, 600, 400); // Compensate for old golden tester padding
|
||||
|
||||
Future<void> testPath(
|
||||
Path path,
|
||||
String goldenFileName, {
|
||||
SurfacePaint? paint,
|
||||
PaintMode mode = PaintMode.kStrokeAndFill,
|
||||
}) async {
|
||||
const Rect canvasBounds = Rect.fromLTWH(0, 0, 600, 400);
|
||||
final BitmapCanvas bitmapCanvas = BitmapCanvas(canvasBounds, RenderStrategy());
|
||||
final RecordingCanvas canvas = RecordingCanvas(canvasBounds);
|
||||
|
||||
final bool enableFill = mode == PaintMode.kStrokeAndFill || mode == PaintMode.kFill;
|
||||
if (enableFill) {
|
||||
paint ??=
|
||||
SurfacePaint()
|
||||
..color = const Color(0x807F7F7F)
|
||||
..style = PaintingStyle.fill;
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
if (mode == PaintMode.kStrokeAndFill || mode == PaintMode.kStroke) {
|
||||
paint =
|
||||
SurfacePaint()
|
||||
..strokeWidth = 2
|
||||
..color = enableFill ? const Color(0xFFFF0000) : const Color(0xFF000000)
|
||||
..style = PaintingStyle.stroke;
|
||||
}
|
||||
|
||||
if (mode == PaintMode.kStrokeWidthOnly) {
|
||||
paint =
|
||||
SurfacePaint()
|
||||
..color = const Color(0xFF4060E0)
|
||||
..strokeWidth = 10;
|
||||
}
|
||||
|
||||
canvas.drawPath(path, paint!);
|
||||
|
||||
final DomElement svgElement = pathToSvgElement(path, paint, enableFill);
|
||||
|
||||
canvas.endRecording();
|
||||
canvas.apply(bitmapCanvas, canvasBounds);
|
||||
|
||||
final DomElement sceneElement = createDomElement('flt-scene');
|
||||
domDocument.body!.append(sceneElement);
|
||||
if (isIosSafari) {
|
||||
// Shrink to fit on the iPhone screen.
|
||||
sceneElement.style.position = 'absolute';
|
||||
sceneElement.style.transformOrigin = '0 0 0';
|
||||
sceneElement.style.transform = 'scale(0.3)';
|
||||
}
|
||||
sceneElement.append(bitmapCanvas.rootElement);
|
||||
sceneElement.append(svgElement);
|
||||
|
||||
await matchGoldenFile('$goldenFileName.png', region: region);
|
||||
|
||||
bitmapCanvas.rootElement.remove();
|
||||
svgElement.remove();
|
||||
}
|
||||
|
||||
tearDown(() {
|
||||
domDocument.body!.clearChildren();
|
||||
});
|
||||
|
||||
test('render line strokes', () async {
|
||||
final Path path = Path();
|
||||
path.moveTo(50, 60);
|
||||
path.lineTo(200, 300);
|
||||
await testPath(
|
||||
path,
|
||||
'svg_stroke_line',
|
||||
paint:
|
||||
SurfacePaint()
|
||||
..color = const Color(0xFFFF0000)
|
||||
..strokeWidth = 2.0
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
});
|
||||
|
||||
test('render quad bezier curve', () async {
|
||||
final Path path = Path();
|
||||
path.moveTo(50, 60);
|
||||
path.quadraticBezierTo(200, 60, 50, 200);
|
||||
await testPath(path, 'svg_quad_bezier');
|
||||
});
|
||||
|
||||
test('render cubic curve', () async {
|
||||
final Path path = Path();
|
||||
path.moveTo(50, 60);
|
||||
path.cubicTo(200, 60, -100, -50, 150, 200);
|
||||
await testPath(path, 'svg_cubic_bezier');
|
||||
});
|
||||
|
||||
test('render arcs', () async {
|
||||
final List<ArcSample> arcs = <ArcSample>[
|
||||
ArcSample(Offset.zero, distance: 20),
|
||||
ArcSample(const Offset(200, 0), largeArc: true, distance: 20),
|
||||
ArcSample(Offset.zero, clockwise: true, distance: 20),
|
||||
ArcSample(const Offset(200, 0), largeArc: true, clockwise: true, distance: 20),
|
||||
ArcSample(Offset.zero, distance: -20),
|
||||
ArcSample(const Offset(200, 0), largeArc: true, distance: -20),
|
||||
ArcSample(Offset.zero, clockwise: true, distance: -20),
|
||||
ArcSample(const Offset(200, 0), largeArc: true, clockwise: true, distance: -20),
|
||||
];
|
||||
int sampleIndex = 0;
|
||||
for (final ArcSample sample in arcs) {
|
||||
++sampleIndex;
|
||||
final Path path = sample.createPath();
|
||||
await testPath(path, 'svg_arc_$sampleIndex');
|
||||
}
|
||||
});
|
||||
|
||||
test('render rect', () async {
|
||||
final Path path = Path();
|
||||
path.addRect(const Rect.fromLTRB(15, 15, 60, 20));
|
||||
path.addRect(const Rect.fromLTRB(35, 160, 15, 100));
|
||||
await testPath(path, 'svg_rect');
|
||||
});
|
||||
|
||||
test('render notch', () async {
|
||||
final Path path = Path();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(83, 0);
|
||||
path.quadraticBezierTo(98, 0, 99.97, 7.8);
|
||||
path.arcToPoint(const Offset(162, 7.8), radius: const Radius.circular(32), clockwise: false);
|
||||
path.lineTo(200, 7.8);
|
||||
path.lineTo(200, 80);
|
||||
path.lineTo(0, 80);
|
||||
path.lineTo(0, 10);
|
||||
await testPath(path, 'svg_notch');
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/70980
|
||||
test('render notch', () async {
|
||||
const double w = 0.7;
|
||||
final Path path = Path();
|
||||
path.moveTo(0.5, 14);
|
||||
path.conicTo(0.5, 10.5, 4, 10.5, w);
|
||||
path.moveTo(4, 10.5);
|
||||
path.lineTo(6.5, 10.5);
|
||||
path.moveTo(36.0, 10.5);
|
||||
path.lineTo(158, 10.5);
|
||||
path.conicTo(161.5, 10.5, 161.5, 14, w);
|
||||
path.moveTo(161.5, 14);
|
||||
path.lineTo(161.5, 48);
|
||||
path.conicTo(161.5, 51.5, 158, 51.5, w);
|
||||
path.lineTo(4, 51.5);
|
||||
path.conicTo(0.5, 51.5, 0.5, 48, w);
|
||||
path.lineTo(0.5, 14);
|
||||
await testPath(path, 'svg_editoutline', mode: PaintMode.kStroke);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/74416
|
||||
test('render stroke', () async {
|
||||
final Path path = Path();
|
||||
path.moveTo(20, 20);
|
||||
path.lineTo(200, 200);
|
||||
await testPath(path, 'svg_stroke_width', mode: PaintMode.kStrokeWidthOnly);
|
||||
});
|
||||
}
|
||||
|
||||
DomElement pathToSvgElement(Path path, Paint paint, bool enableFill) {
|
||||
final Rect bounds = path.getBounds();
|
||||
final SVGSVGElement root = createSVGSVGElement();
|
||||
root.style.transform = 'translate(200px, 0px)';
|
||||
root.setAttribute('viewBox', '0 0 ${bounds.right} ${bounds.bottom}');
|
||||
root.width!.baseVal!.newValueSpecifiedUnits(svgLengthTypeNumber, bounds.right);
|
||||
root.height!.baseVal!.newValueSpecifiedUnits(svgLengthTypeNumber, bounds.bottom);
|
||||
|
||||
final SVGPathElement pathElement = createSVGPathElement();
|
||||
root.append(pathElement);
|
||||
if (paint.style == PaintingStyle.stroke || paint.strokeWidth != 0.0) {
|
||||
pathElement.setAttribute('stroke', paint.color.toCssString());
|
||||
pathElement.setAttribute('stroke-width', paint.strokeWidth);
|
||||
if (!enableFill) {
|
||||
pathElement.setAttribute('fill', 'none');
|
||||
}
|
||||
}
|
||||
if (paint.style == PaintingStyle.fill) {
|
||||
pathElement.setAttribute('fill', paint.color.toCssString());
|
||||
}
|
||||
pathElement.setAttribute(
|
||||
'd',
|
||||
pathToSvg((path as SurfacePath).pathRef),
|
||||
); // This is what we're testing!
|
||||
return root;
|
||||
}
|
||||
|
||||
class ArcSample {
|
||||
ArcSample(this.offset, {this.largeArc = false, this.clockwise = false, this.distance = 0});
|
||||
|
||||
final Offset offset;
|
||||
final bool largeArc;
|
||||
final bool clockwise;
|
||||
final double distance;
|
||||
|
||||
Path createPath() {
|
||||
final Offset startP = Offset(75 - distance + offset.dx, 75 - distance + offset.dy);
|
||||
final Offset endP = Offset(75.0 + distance + offset.dx, 75.0 + distance + offset.dy);
|
||||
final Path path = Path();
|
||||
path.moveTo(startP.dx, startP.dy);
|
||||
path.arcToPoint(
|
||||
endP,
|
||||
rotation: 60,
|
||||
radius: const Radius.elliptical(40, 60),
|
||||
largeArc: largeArc,
|
||||
clockwise: clockwise,
|
||||
);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
// 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 'dart:math' as math;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' hide TextStyle;
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
import 'screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const double screenWidth = 600.0;
|
||||
const double screenHeight = 800.0;
|
||||
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
test('Should draw transformed line.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
final Path path = Path();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(300, 200);
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF404000),
|
||||
);
|
||||
final Path transformedPath = Path();
|
||||
final Matrix4 testMatrixTranslateRotate = Matrix4.rotationZ(math.pi * 30.0 / 180.0)
|
||||
..translate(100, 20);
|
||||
transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.toFloat64());
|
||||
rc.drawPath(
|
||||
transformedPath,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color.fromRGBO(0, 128, 255, 1.0),
|
||||
);
|
||||
await canvasScreenshot(rc, 'path_transform_with_line', canvasRect: screenRect);
|
||||
});
|
||||
|
||||
test('Should draw transformed line.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
final Path path = Path();
|
||||
path.addRect(const Rect.fromLTRB(50, 40, 300, 100));
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF404000),
|
||||
);
|
||||
final Path transformedPath = Path();
|
||||
final Matrix4 testMatrixTranslateRotate = Matrix4.rotationZ(math.pi * 30.0 / 180.0)
|
||||
..translate(100, 20);
|
||||
transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.toFloat64());
|
||||
rc.drawPath(
|
||||
transformedPath,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color.fromRGBO(0, 128, 255, 1.0),
|
||||
);
|
||||
await canvasScreenshot(rc, 'path_transform_with_rect', canvasRect: screenRect);
|
||||
});
|
||||
|
||||
test('Should draw transformed quadratic curve.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
final Path path = Path();
|
||||
path.moveTo(100, 100);
|
||||
path.quadraticBezierTo(100, 300, 400, 300);
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF404000),
|
||||
);
|
||||
final Path transformedPath = Path();
|
||||
final Matrix4 testMatrixTranslateRotate = Matrix4.rotationZ(math.pi * 30.0 / 180.0)
|
||||
..translate(100, -80);
|
||||
transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.toFloat64());
|
||||
rc.drawPath(
|
||||
transformedPath,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color.fromRGBO(0, 128, 255, 1.0),
|
||||
);
|
||||
await canvasScreenshot(rc, 'path_transform_with_quadratic_curve', canvasRect: screenRect);
|
||||
});
|
||||
|
||||
test('Should draw transformed conic.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
const double yStart = 20;
|
||||
|
||||
const Offset p0 = Offset(25, yStart + 25);
|
||||
const Offset pc = Offset(60, yStart + 150);
|
||||
const Offset p2 = Offset(100, yStart + 50);
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(p0.dx, p0.dy);
|
||||
path.conicTo(pc.dx, pc.dy, p2.dx, p2.dy, 0.5);
|
||||
path.close();
|
||||
path.moveTo(p0.dx, p0.dy + 100);
|
||||
path.conicTo(pc.dx, pc.dy + 100, p2.dx, p2.dy + 100, 10);
|
||||
path.close();
|
||||
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF404000),
|
||||
);
|
||||
final Path transformedPath = Path();
|
||||
final Matrix4 testMatrixTranslateRotate = Matrix4.rotationZ(math.pi * 30.0 / 180.0)
|
||||
..translate(100, -80);
|
||||
transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.toFloat64());
|
||||
rc.drawPath(
|
||||
transformedPath,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color.fromRGBO(0, 128, 255, 1.0),
|
||||
);
|
||||
await canvasScreenshot(rc, 'path_transform_with_conic', canvasRect: screenRect);
|
||||
});
|
||||
|
||||
test('Should draw transformed arc.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(350, 280);
|
||||
path.arcToPoint(
|
||||
const Offset(450, 90),
|
||||
radius: const Radius.elliptical(200, 50),
|
||||
rotation: -math.pi / 6.0,
|
||||
largeArc: true,
|
||||
);
|
||||
path.close();
|
||||
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF404000),
|
||||
);
|
||||
|
||||
final Path transformedPath = Path();
|
||||
final Matrix4 testMatrixTranslateRotate = Matrix4.rotationZ(math.pi * 30.0 / 180.0)
|
||||
..translate(100, 10);
|
||||
transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.toFloat64());
|
||||
rc.drawPath(
|
||||
transformedPath,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color.fromRGBO(0, 128, 255, 1.0),
|
||||
);
|
||||
await canvasScreenshot(rc, 'path_transform_with_arc', canvasRect: screenRect);
|
||||
});
|
||||
|
||||
test('Should draw transformed rrect.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
|
||||
final Path path = Path();
|
||||
path.addRRect(RRect.fromLTRBR(50, 50, 300, 200, const Radius.elliptical(4, 8)));
|
||||
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF404000),
|
||||
);
|
||||
|
||||
final Path transformedPath = Path();
|
||||
final Matrix4 testMatrixTranslateRotate = Matrix4.rotationZ(math.pi * 30.0 / 180.0)
|
||||
..translate(100, -80);
|
||||
transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.toFloat64());
|
||||
rc.drawPath(
|
||||
transformedPath,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color.fromRGBO(0, 128, 255, 1.0),
|
||||
);
|
||||
await canvasScreenshot(rc, 'path_transform_with_rrect', canvasRect: screenRect);
|
||||
});
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// 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 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
group('Picture', () {
|
||||
test('toImage produces an image', () async {
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(
|
||||
const ui.Rect.fromLTRB(0, 0, 200, 100),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(100, 50),
|
||||
40,
|
||||
SurfacePaint()..color = const ui.Color.fromARGB(255, 255, 100, 100),
|
||||
);
|
||||
final ui.Picture picture = recorder.endRecording();
|
||||
final HtmlImage image = await picture.toImage(200, 100) as HtmlImage;
|
||||
expect(image, isNotNull);
|
||||
domDocument.body!
|
||||
..style.margin = '0'
|
||||
..append(image.imgElement);
|
||||
try {
|
||||
await matchGoldenFile(
|
||||
'picture_to_image.png',
|
||||
region: const ui.Rect.fromLTRB(0, 0, 200, 100),
|
||||
);
|
||||
} finally {
|
||||
image.imgElement.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,777 +0,0 @@
|
||||
// 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 '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' hide ColorSpace;
|
||||
import 'package:ui/ui.dart' hide TextStyle;
|
||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../common/matchers.dart';
|
||||
import '../common/test_initialization.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(withImplicitView: true, setUpTestViewDimensions: false);
|
||||
|
||||
const double screenWidth = 600.0;
|
||||
const double screenHeight = 800.0;
|
||||
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
final SurfacePaint testPaint = SurfacePaint()..color = const Color(0xFFFF0000);
|
||||
|
||||
// Commit a recording canvas to a bitmap, and compare with the expected
|
||||
Future<void> checkScreenshot(
|
||||
RecordingCanvas rc,
|
||||
String fileName, {
|
||||
Rect region = const Rect.fromLTWH(0, 0, 500, 500),
|
||||
}) async {
|
||||
final EngineCanvas engineCanvas = BitmapCanvas(screenRect, RenderStrategy());
|
||||
|
||||
// Draws the estimated bounds so we can spot the bug in Gold.
|
||||
engineCanvas
|
||||
..save()
|
||||
..drawRect(
|
||||
rc.pictureBounds!,
|
||||
SurfacePaintData()
|
||||
..color = const Color.fromRGBO(0, 0, 255, 1.0).value
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.0,
|
||||
)
|
||||
..restore();
|
||||
|
||||
rc.apply(engineCanvas, screenRect);
|
||||
|
||||
// Wrap in <flt-scene> so that our CSS selectors kick in.
|
||||
final DomElement sceneElement = createDomElement('flt-scene');
|
||||
if (isIosSafari) {
|
||||
// Shrink to fit on the iPhone screen.
|
||||
sceneElement.style.position = 'absolute';
|
||||
sceneElement.style.transformOrigin = '0 0 0';
|
||||
sceneElement.style.transform = 'scale(0.3)';
|
||||
}
|
||||
try {
|
||||
sceneElement.append(engineCanvas.rootElement);
|
||||
domDocument.body!.append(sceneElement);
|
||||
await matchGoldenFile('paint_bounds_for_$fileName.png', region: region);
|
||||
} finally {
|
||||
// The page is reused across tests, so remove the element after taking the
|
||||
// screenshot.
|
||||
sceneElement.remove();
|
||||
}
|
||||
}
|
||||
|
||||
test('Empty canvas reports correct paint bounds', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTWH(1, 2, 300, 400));
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, Rect.zero);
|
||||
await checkScreenshot(rc, 'empty_canvas');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for draw line', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawLine(const Offset(50, 100), const Offset(120, 140), testPaint);
|
||||
rc.endRecording();
|
||||
// The off by one is due to the minimum stroke width of 1.
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(49, 99, 121, 141));
|
||||
await checkScreenshot(rc, 'draw_line');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for draw line when line exceeds limits', () async {
|
||||
// Uses max bounds when computing paint bounds
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawLine(const Offset(50, 100), const Offset(screenWidth + 100.0, 140), testPaint);
|
||||
rc.endRecording();
|
||||
// The off by one is due to the minimum stroke width of 1.
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(49.0, 99.0, screenWidth, 141.0));
|
||||
await checkScreenshot(rc, 'draw_line_exceeding_limits');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for draw rect', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawRect(const Rect.fromLTRB(10, 20, 30, 40), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(10, 20, 30, 40));
|
||||
await checkScreenshot(rc, 'draw_rect');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for draw rect when exceeds limits', () async {
|
||||
// Uses max bounds when computing paint bounds
|
||||
RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawRect(const Rect.fromLTRB(10, 20, 30 + screenWidth, 40 + screenHeight), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(10, 20, screenWidth, screenHeight));
|
||||
|
||||
rc = RecordingCanvas(screenRect);
|
||||
rc.drawRect(const Rect.fromLTRB(-200, -100, 30, 40), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(0, 0, 30, 40));
|
||||
await checkScreenshot(rc, 'draw_rect_exceeding_limits');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for translate', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.translate(5, 7);
|
||||
rc.drawRect(const Rect.fromLTRB(10, 20, 30, 40), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(15, 27, 35, 47));
|
||||
await checkScreenshot(rc, 'translate');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for scale', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.scale(2, 2);
|
||||
rc.drawRect(const Rect.fromLTRB(10, 20, 30, 40), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(20, 40, 60, 80));
|
||||
await checkScreenshot(rc, 'scale');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for rotate', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.rotate(math.pi / 4.0);
|
||||
rc.drawLine(const Offset(1, 0), Offset(50 * math.sqrt(2) - 1, 0), testPaint);
|
||||
rc.endRecording();
|
||||
// The extra 0.7 is due to stroke width of 1 rotated by 45 degrees.
|
||||
expect(rc.pictureBounds, within(distance: 0.1, from: const Rect.fromLTRB(0, 0, 50.7, 50.7)));
|
||||
await checkScreenshot(rc, 'rotate');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for horizontal skew', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.skew(1.0, 0.0);
|
||||
rc.drawRect(const Rect.fromLTRB(20, 20, 40, 40), testPaint);
|
||||
rc.endRecording();
|
||||
expect(
|
||||
rc.pictureBounds,
|
||||
within(distance: 0.1, from: const Rect.fromLTRB(40.0, 20.0, 80.0, 40.0)),
|
||||
);
|
||||
await checkScreenshot(rc, 'skew_horizontally');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for vertical skew', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.skew(0.0, 1.0);
|
||||
rc.drawRect(const Rect.fromLTRB(20, 20, 40, 40), testPaint);
|
||||
rc.endRecording();
|
||||
expect(
|
||||
rc.pictureBounds,
|
||||
within(distance: 0.1, from: const Rect.fromLTRB(20.0, 40.0, 40.0, 80.0)),
|
||||
);
|
||||
await checkScreenshot(rc, 'skew_vertically');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for a complex transform', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
final Float32List matrix = Float32List(16);
|
||||
// translate(210, 220) , scale(2, 3), rotate(math.pi / 4.0)
|
||||
matrix[0] = 1.4;
|
||||
matrix[1] = 2.12;
|
||||
matrix[2] = 0.0;
|
||||
matrix[3] = 0.0;
|
||||
matrix[4] = -1.4;
|
||||
matrix[5] = 2.12;
|
||||
matrix[6] = 0.0;
|
||||
matrix[7] = 0.0;
|
||||
matrix[8] = 0.0;
|
||||
matrix[9] = 0.0;
|
||||
matrix[10] = 2.0;
|
||||
matrix[11] = 0.0;
|
||||
matrix[12] = 210.0;
|
||||
matrix[13] = 220.0;
|
||||
matrix[14] = 0.0;
|
||||
matrix[15] = 1.0;
|
||||
rc.transform(matrix);
|
||||
rc.drawRect(const Rect.fromLTRB(10, 20, 30, 40), testPaint);
|
||||
rc.endRecording();
|
||||
expect(
|
||||
rc.pictureBounds,
|
||||
within(distance: 0.001, from: const Rect.fromLTRB(168.0, 283.6, 224.0, 368.4)),
|
||||
);
|
||||
await checkScreenshot(rc, 'complex_transform');
|
||||
});
|
||||
|
||||
test('drawPaint should cover full size', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawPaint(testPaint);
|
||||
rc.drawRect(const Rect.fromLTRB(10, 20, 30, 40), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, screenRect);
|
||||
await checkScreenshot(rc, 'draw_paint');
|
||||
});
|
||||
|
||||
test('drawColor should cover full size', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
final SurfacePaint testPaint = SurfacePaint()..color = const Color(0xFF80FF00);
|
||||
rc.drawRect(const Rect.fromLTRB(10, 20, 30, 40), testPaint);
|
||||
rc.drawColor(const Color(0xFFFF0000), BlendMode.multiply);
|
||||
rc.drawRect(const Rect.fromLTRB(10, 60, 30, 80), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, screenRect);
|
||||
await checkScreenshot(rc, 'draw_color');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for draw oval', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawOval(const Rect.fromLTRB(10, 20, 30, 40), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(10, 20, 30, 40));
|
||||
await checkScreenshot(rc, 'draw_oval');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for draw round rect', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawRRect(
|
||||
RRect.fromRectAndRadius(const Rect.fromLTRB(10, 20, 30, 40), const Radius.circular(5.0)),
|
||||
testPaint,
|
||||
);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(10, 20, 30, 40));
|
||||
await checkScreenshot(rc, 'draw_round_rect');
|
||||
});
|
||||
|
||||
test('Computes empty paint bounds when inner rect outside of outer rect for '
|
||||
'drawDRRect', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawDRRect(
|
||||
RRect.fromRectAndCorners(const Rect.fromLTRB(10, 20, 30, 40)),
|
||||
RRect.fromRectAndCorners(const Rect.fromLTRB(1, 2, 3, 4)),
|
||||
testPaint,
|
||||
);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, Rect.zero);
|
||||
await checkScreenshot(rc, 'draw_drrect_empty');
|
||||
});
|
||||
|
||||
test('Computes paint bounds using outer rect for drawDRRect', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawDRRect(
|
||||
RRect.fromRectAndCorners(const Rect.fromLTRB(10, 20, 30, 40)),
|
||||
RRect.fromRectAndCorners(const Rect.fromLTRB(12, 22, 28, 38)),
|
||||
testPaint,
|
||||
);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(10, 20, 30, 40));
|
||||
await checkScreenshot(rc, 'draw_drrect');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for draw circle', () async {
|
||||
// Paint bounds of one circle.
|
||||
RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawCircle(const Offset(20, 20), 10.0, testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(10.0, 10.0, 30.0, 30.0));
|
||||
|
||||
// Paint bounds of a union of two circles.
|
||||
rc = RecordingCanvas(screenRect);
|
||||
rc.drawCircle(const Offset(20, 20), 10.0, testPaint);
|
||||
rc.drawCircle(const Offset(200, 300), 100.0, testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(10.0, 10.0, 300.0, 400.0));
|
||||
await checkScreenshot(rc, 'draw_circle');
|
||||
});
|
||||
|
||||
test('Computes paint bounds for draw image', () {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawImage(TestImage(), const Offset(50, 100), SurfacePaint());
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(50.0, 100.0, 70.0, 110.0));
|
||||
});
|
||||
|
||||
test('Computes paint bounds for draw image rect', () {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.drawImageRect(
|
||||
TestImage(),
|
||||
const Rect.fromLTRB(1, 1, 20, 10),
|
||||
const Rect.fromLTRB(5, 6, 400, 500),
|
||||
SurfacePaint(),
|
||||
);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(5.0, 6.0, 400.0, 500.0));
|
||||
});
|
||||
|
||||
test(
|
||||
'Computes paint bounds for single-line draw paragraph',
|
||||
() async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
final Paragraph paragraph = createTestParagraph();
|
||||
const double textLeft = 5.0;
|
||||
const double textTop = 7.0;
|
||||
const double widthConstraint = 300.0;
|
||||
paragraph.layout(const ParagraphConstraints(width: widthConstraint));
|
||||
rc.drawParagraph(paragraph, const Offset(textLeft, textTop));
|
||||
rc.endRecording();
|
||||
expect(
|
||||
rc.pictureBounds!.width,
|
||||
lessThan(widthConstraint),
|
||||
reason:
|
||||
'The given width constraint $widthConstraint is more than the '
|
||||
'test string needs, so the width of the visible text is actually '
|
||||
'smaller than the given width.',
|
||||
);
|
||||
expect(
|
||||
rc.pictureBounds,
|
||||
Rect.fromLTRB(textLeft, textTop, textLeft + paragraph.maxIntrinsicWidth, 21.0),
|
||||
);
|
||||
await checkScreenshot(rc, 'draw_paragraph');
|
||||
}, // TODO(mdebbar): https://github.com/flutter/flutter/issues/65789
|
||||
skip:
|
||||
ui_web.browser.browserEngine == ui_web.BrowserEngine.webkit &&
|
||||
ui_web.browser.operatingSystem == ui_web.OperatingSystem.iOs,
|
||||
);
|
||||
|
||||
test(
|
||||
'Computes paint bounds for multi-line draw paragraph',
|
||||
() async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
final Paragraph paragraph = createTestParagraph();
|
||||
const double textLeft = 5.0;
|
||||
const double textTop = 7.0;
|
||||
// Do not go lower than the shortest word.
|
||||
const double widthConstraint = 130.0;
|
||||
paragraph.layout(const ParagraphConstraints(width: widthConstraint));
|
||||
rc.drawParagraph(paragraph, const Offset(textLeft, textTop));
|
||||
rc.endRecording();
|
||||
|
||||
const double fontWidth = 14;
|
||||
const int lettersInLongestWord = 9;
|
||||
const double longestLineWidth = lettersInLongestWord * fontWidth;
|
||||
expect(
|
||||
rc.pictureBounds!.width,
|
||||
lessThan(widthConstraint),
|
||||
reason:
|
||||
'The test string "A short sentence." is broken up into two lines, '
|
||||
'"A short" and "sentence.". The longest line contains '
|
||||
'$lettersInLongestWord characters, each ${fontWidth}px wide. '
|
||||
'That line is ${longestLineWidth}px wide, which is less than '
|
||||
'$widthConstraint.',
|
||||
);
|
||||
expect(
|
||||
rc.pictureBounds,
|
||||
const Rect.fromLTRB(textLeft, textTop, textLeft + longestLineWidth, 35.0),
|
||||
);
|
||||
await checkScreenshot(rc, 'draw_paragraph_multi_line');
|
||||
}, // TODO(mdebbar): https://github.com/flutter/flutter/issues/65789
|
||||
skip:
|
||||
ui_web.browser.browserEngine == ui_web.BrowserEngine.webkit &&
|
||||
ui_web.browser.operatingSystem == ui_web.OperatingSystem.iOs,
|
||||
);
|
||||
|
||||
test('Should exclude painting outside simple clipRect', () async {
|
||||
// One clipped line.
|
||||
RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.clipRect(const Rect.fromLTRB(50, 50, 100, 100), ClipOp.intersect);
|
||||
rc.drawLine(const Offset(10, 11), const Offset(20, 21), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, Rect.zero);
|
||||
|
||||
// Two clipped lines.
|
||||
rc = RecordingCanvas(screenRect);
|
||||
rc.clipRect(const Rect.fromLTRB(50, 50, 100, 100), ClipOp.intersect);
|
||||
rc.drawLine(const Offset(10, 11), const Offset(20, 21), testPaint);
|
||||
rc.drawLine(const Offset(52, 53), const Offset(55, 56), testPaint);
|
||||
rc.endRecording();
|
||||
|
||||
// Extra pixel due to default line length
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(51, 52, 56, 57));
|
||||
await checkScreenshot(rc, 'clip_rect_simple');
|
||||
});
|
||||
|
||||
test('Should include intersection of clipRect and painting', () async {
|
||||
RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.clipRect(const Rect.fromLTRB(50, 50, 100, 100), ClipOp.intersect);
|
||||
rc.drawRect(const Rect.fromLTRB(20, 60, 120, 70), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(50, 60, 100, 70));
|
||||
await checkScreenshot(rc, 'clip_rect_intersects_paint_left_to_right');
|
||||
|
||||
rc = RecordingCanvas(screenRect);
|
||||
rc.clipRect(const Rect.fromLTRB(50, 50, 100, 100), ClipOp.intersect);
|
||||
rc.drawRect(const Rect.fromLTRB(60, 20, 70, 200), testPaint);
|
||||
rc.endRecording();
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(60, 50, 70, 100));
|
||||
await checkScreenshot(rc, 'clip_rect_intersects_paint_top_to_bottom');
|
||||
});
|
||||
|
||||
test('Should intersect rects for multiple clipRect calls', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
rc.clipRect(const Rect.fromLTRB(50, 50, 100, 100), ClipOp.intersect);
|
||||
rc.scale(2.0, 2.0);
|
||||
rc.clipRect(const Rect.fromLTRB(30, 30, 45, 45), ClipOp.intersect);
|
||||
rc.drawRect(const Rect.fromLTRB(10, 30, 60, 35), testPaint);
|
||||
rc.endRecording();
|
||||
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(60, 60, 90, 70));
|
||||
await checkScreenshot(rc, 'clip_rects_intersect');
|
||||
});
|
||||
|
||||
// drawShadow
|
||||
test('Computes paint bounds for drawShadow', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
final Path path = Path();
|
||||
path.addRect(const Rect.fromLTRB(20, 30, 100, 110));
|
||||
rc.drawShadow(path, const Color(0xFFFF0000), 2.0, true);
|
||||
rc.endRecording();
|
||||
|
||||
expect(
|
||||
rc.pictureBounds,
|
||||
within(distance: 0.05, from: const Rect.fromLTRB(0.0, 8.5, 123.5, 134.1)),
|
||||
);
|
||||
await checkScreenshot(rc, 'path_with_shadow');
|
||||
});
|
||||
|
||||
test('Clip with negative scale reports correct paint bounds', () async {
|
||||
// The following draws a filled rectangle that occupies the bottom half of
|
||||
// the canvas. Notice that both the clip and the rectangle are drawn
|
||||
// forward. What makes them appear at the bottom is the translation and a
|
||||
// vertical flip via a negative scale. This replicates the Material
|
||||
// overscroll glow effect at the bottom of a list, where it is drawn upside
|
||||
// down.
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
rc
|
||||
..translate(0, 100)
|
||||
..scale(1, -1)
|
||||
..clipRect(const Rect.fromLTRB(0, 0, 100, 50), ClipOp.intersect)
|
||||
..drawRect(const Rect.fromLTRB(0, 0, 100, 100), SurfacePaint());
|
||||
rc.endRecording();
|
||||
|
||||
expect(rc.pictureBounds, const Rect.fromLTRB(0.0, 50.0, 100.0, 100.0));
|
||||
await checkScreenshot(rc, 'scale_negative');
|
||||
});
|
||||
|
||||
test('Clip with a rotation reports correct paint bounds', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
rc
|
||||
..translate(50, 50)
|
||||
..rotate(math.pi / 4.0)
|
||||
..clipRect(const Rect.fromLTWH(-20, -20, 40, 40), ClipOp.intersect)
|
||||
..drawRect(const Rect.fromLTWH(-80, -80, 160, 160), SurfacePaint());
|
||||
rc.endRecording();
|
||||
|
||||
expect(
|
||||
rc.pictureBounds,
|
||||
within(
|
||||
distance: 0.001,
|
||||
from: Rect.fromCircle(center: const Offset(50, 50), radius: 20 * math.sqrt(2)),
|
||||
),
|
||||
);
|
||||
await checkScreenshot(rc, 'clip_rect_rotated');
|
||||
});
|
||||
|
||||
test('Rotated line reports correct paint bounds', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
rc
|
||||
..translate(50, 50)
|
||||
..rotate(math.pi / 4.0)
|
||||
..drawLine(Offset.zero, const Offset(20, 20), SurfacePaint());
|
||||
rc.endRecording();
|
||||
|
||||
expect(
|
||||
rc.pictureBounds,
|
||||
within(distance: 0.1, from: const Rect.fromLTRB(34.4, 48.6, 65.6, 79.7)),
|
||||
);
|
||||
await checkScreenshot(rc, 'line_rotated');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/46339.
|
||||
test('Should draw a Rect for straight line when strokeWidth is zero.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(screenRect);
|
||||
|
||||
final Path path = Path();
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 0.0
|
||||
..color = const Color(0xFFFF0000);
|
||||
path.moveTo(10, 10);
|
||||
path.lineTo(90, 10);
|
||||
rc.drawPath(path, paint);
|
||||
rc.endRecording();
|
||||
// Should draw a Rect
|
||||
final List<PaintCommand> commands = rc.debugPaintCommands;
|
||||
expect(commands.length, 1);
|
||||
expect(commands.first, isA<PaintDrawRect>());
|
||||
// Should inflate picture bounds
|
||||
expect(rc.pictureBounds, within(distance: 0.1, from: const Rect.fromLTRB(10, 10, 90, 11)));
|
||||
await checkScreenshot(rc, 'path_straight_line_with_zero_stroke_width');
|
||||
});
|
||||
|
||||
test('Should support reusing path and reset when drawing into canvas.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 100, 100));
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(3, 0);
|
||||
path.lineTo(100, 97);
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFFFF0000),
|
||||
);
|
||||
path.reset();
|
||||
path.moveTo(0, 3);
|
||||
path.lineTo(97, 100);
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF00FF00),
|
||||
);
|
||||
rc.endRecording();
|
||||
await checkScreenshot(rc, 'reuse_path');
|
||||
});
|
||||
|
||||
test('Should draw RRect after line when beginning new path.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 200, 400));
|
||||
rc.save();
|
||||
rc.translate(50.0, 100.0);
|
||||
final Path path = Path();
|
||||
// Draw a vertical small line (caret).
|
||||
path.moveTo(8, 4);
|
||||
path.lineTo(8, 24);
|
||||
// Draw round rect below caret.
|
||||
path.addRRect(RRect.fromLTRBR(0.5, 100.5, 80.7, 150.7, const Radius.circular(10)));
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF404000),
|
||||
);
|
||||
rc.restore();
|
||||
rc.endRecording();
|
||||
await checkScreenshot(rc, 'path_with_line_and_roundrect');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/64371.
|
||||
test('Should draw line following a polygon without closing path.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 200, 400));
|
||||
rc.save();
|
||||
rc.translate(50.0, 100.0);
|
||||
final Path path = Path();
|
||||
// Draw a vertical small line (caret).
|
||||
path.addPolygon(const <Offset>[Offset(0, 10), Offset(20, 5), Offset(50, 10)], false);
|
||||
path.lineTo(60, 80);
|
||||
path.lineTo(0, 80);
|
||||
path.close();
|
||||
rc.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0
|
||||
..color = const Color(0xFF404000),
|
||||
);
|
||||
rc.restore();
|
||||
rc.endRecording();
|
||||
await checkScreenshot(rc, 'path_with_addpolygon');
|
||||
});
|
||||
|
||||
test('should include paint spread in bounds estimates', () async {
|
||||
final SurfaceSceneBuilder sb = SurfaceSceneBuilder();
|
||||
|
||||
final List<PaintSpreadPainter> painters = <PaintSpreadPainter>[
|
||||
(RecordingCanvas canvas, SurfacePaint paint) {
|
||||
canvas.drawLine(Offset.zero, const Offset(20.0, 20.0), paint);
|
||||
},
|
||||
(RecordingCanvas canvas, SurfacePaint paint) {
|
||||
canvas.drawRect(const Rect.fromLTRB(0.0, 0.0, 20.0, 20.0), paint);
|
||||
},
|
||||
(RecordingCanvas canvas, SurfacePaint paint) {
|
||||
canvas.drawRRect(RRect.fromLTRBR(0.0, 0.0, 20.0, 20.0, const Radius.circular(7.0)), paint);
|
||||
},
|
||||
(RecordingCanvas canvas, SurfacePaint paint) {
|
||||
canvas.drawDRRect(
|
||||
RRect.fromLTRBR(0.0, 0.0, 20.0, 20.0, const Radius.circular(5.0)),
|
||||
RRect.fromLTRBR(4.0, 4.0, 16.0, 16.0, const Radius.circular(5.0)),
|
||||
paint,
|
||||
);
|
||||
},
|
||||
(RecordingCanvas canvas, SurfacePaint paint) {
|
||||
canvas.drawOval(const Rect.fromLTRB(0.0, 5.0, 20.0, 15.0), paint);
|
||||
},
|
||||
(RecordingCanvas canvas, SurfacePaint paint) {
|
||||
canvas.drawCircle(const Offset(10.0, 10.0), 10.0, paint);
|
||||
},
|
||||
(RecordingCanvas canvas, SurfacePaint paint) {
|
||||
final SurfacePath path =
|
||||
SurfacePath()
|
||||
..moveTo(10, 0)
|
||||
..lineTo(20, 10)
|
||||
..lineTo(10, 20)
|
||||
..lineTo(0, 10)
|
||||
..close();
|
||||
canvas.drawPath(path, paint);
|
||||
},
|
||||
|
||||
// Images are not affected by mask filter or stroke width. They use image
|
||||
// filter instead.
|
||||
(RecordingCanvas canvas, SurfacePaint paint) {
|
||||
canvas.drawImage(_createRealTestImage(), Offset.zero, paint);
|
||||
},
|
||||
(RecordingCanvas canvas, SurfacePaint paint) {
|
||||
canvas.drawImageRect(
|
||||
_createRealTestImage(),
|
||||
const Rect.fromLTRB(0, 0, 20, 20),
|
||||
const Rect.fromLTRB(5, 5, 15, 15),
|
||||
paint,
|
||||
);
|
||||
},
|
||||
];
|
||||
|
||||
Picture drawBounds(Rect bounds) {
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(Rect.largest);
|
||||
canvas.drawRect(
|
||||
bounds,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.0
|
||||
..color = const Color.fromARGB(255, 0, 255, 0),
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
for (int i = 0; i < painters.length; i++) {
|
||||
sb.pushOffset(0.0, 20.0 + 60.0 * i);
|
||||
final PaintSpreadPainter painter = painters[i];
|
||||
|
||||
// Paint with zero paint spread.
|
||||
{
|
||||
sb.pushOffset(20.0, 0.0);
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(Rect.largest);
|
||||
final SurfacePaint zeroSpreadPaint = SurfacePaint();
|
||||
painter(canvas, zeroSpreadPaint);
|
||||
sb.addPicture(Offset.zero, recorder.endRecording());
|
||||
sb.addPicture(Offset.zero, drawBounds(canvas.pictureBounds!));
|
||||
sb.pop();
|
||||
}
|
||||
|
||||
// Paint with a thick stroke paint.
|
||||
{
|
||||
sb.pushOffset(80.0, 0.0);
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(Rect.largest);
|
||||
final SurfacePaint thickStrokePaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 5.0;
|
||||
painter(canvas, thickStrokePaint);
|
||||
sb.addPicture(Offset.zero, recorder.endRecording());
|
||||
sb.addPicture(Offset.zero, drawBounds(canvas.pictureBounds!));
|
||||
sb.pop();
|
||||
}
|
||||
|
||||
// Paint with a mask filter blur.
|
||||
{
|
||||
sb.pushOffset(140.0, 0.0);
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(Rect.largest);
|
||||
final SurfacePaint maskFilterBlurPaint =
|
||||
SurfacePaint()..maskFilter = const MaskFilter.blur(BlurStyle.normal, 5.0);
|
||||
painter(canvas, maskFilterBlurPaint);
|
||||
sb.addPicture(Offset.zero, recorder.endRecording());
|
||||
sb.addPicture(Offset.zero, drawBounds(canvas.pictureBounds!));
|
||||
sb.pop();
|
||||
}
|
||||
|
||||
// Paint with a thick stroke paint and a mask filter blur.
|
||||
{
|
||||
sb.pushOffset(200.0, 0.0);
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(Rect.largest);
|
||||
final SurfacePaint thickStrokeAndBlurPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 5.0
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 5.0);
|
||||
painter(canvas, thickStrokeAndBlurPaint);
|
||||
sb.addPicture(Offset.zero, recorder.endRecording());
|
||||
sb.addPicture(Offset.zero, drawBounds(canvas.pictureBounds!));
|
||||
sb.pop();
|
||||
}
|
||||
|
||||
sb.pop();
|
||||
}
|
||||
|
||||
final DomElement sceneElement = sb.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(sceneElement);
|
||||
try {
|
||||
await matchGoldenFile('paint_spread_bounds.png', region: const Rect.fromLTRB(0, 0, 250, 600));
|
||||
} finally {
|
||||
sceneElement.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
typedef PaintSpreadPainter = void Function(RecordingCanvas canvas, SurfacePaint paint);
|
||||
|
||||
const String _base64Encoded20x20TestImage =
|
||||
'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUC'
|
||||
'AIAAAAC64paAAAACXBIWXMAAC4jAAAuIwF4pT92AAAA'
|
||||
'B3RJTUUH5AMFFBksg4i3gQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAj'
|
||||
'SURBVDjLY2TAC/7jlWVioACMah4ZmhnxpyHG0QAb1UyZZgBjWAIm/clP0AAAAABJRU5ErkJggg==';
|
||||
|
||||
HtmlImage _createRealTestImage() {
|
||||
return HtmlImage(
|
||||
createDomHTMLImageElement()..src = 'data:text/plain;base64,$_base64Encoded20x20TestImage',
|
||||
20,
|
||||
20,
|
||||
);
|
||||
}
|
||||
|
||||
class TestImage implements Image {
|
||||
@override
|
||||
int get width => 20;
|
||||
|
||||
@override
|
||||
int get height => 10;
|
||||
|
||||
@override
|
||||
Future<ByteData> toByteData({ImageByteFormat format = ImageByteFormat.rawRgba}) async {
|
||||
throw UnsupportedError('Cannot encode test image');
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '[$width\u00D7$height]';
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
|
||||
@override
|
||||
bool get debugDisposed => false;
|
||||
|
||||
@override
|
||||
Image clone() => this;
|
||||
|
||||
@override
|
||||
bool isCloneOf(Image other) => other == this;
|
||||
|
||||
@override
|
||||
List<StackTrace> /*?*/ debugGetOpenHandleStackTraces() => <StackTrace>[];
|
||||
|
||||
@override
|
||||
ColorSpace get colorSpace => ColorSpace.sRGB;
|
||||
}
|
||||
|
||||
Paragraph createTestParagraph() {
|
||||
final ParagraphBuilder builder = ParagraphBuilder(
|
||||
ParagraphStyle(
|
||||
fontFamily: 'Ahem',
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
);
|
||||
builder.addText('A short sentence.');
|
||||
return builder.build();
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import '../common/mock_engine_canvas.dart';
|
||||
import '../common/test_initialization.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
setUpUnitTests(withImplicitView: true, setUpTestViewDimensions: false);
|
||||
|
||||
late RecordingCanvas underTest;
|
||||
late MockEngineCanvas mockCanvas;
|
||||
const Rect screenRect = Rect.largest;
|
||||
|
||||
setUp(() {
|
||||
underTest = RecordingCanvas(screenRect);
|
||||
mockCanvas = MockEngineCanvas();
|
||||
});
|
||||
|
||||
group('paragraph bounds', () {
|
||||
Paragraph paragraphForBoundsTest(TextAlign alignment) {
|
||||
final ParagraphBuilder builder = ParagraphBuilder(
|
||||
ParagraphStyle(fontFamily: 'Ahem', fontSize: 20, textAlign: alignment),
|
||||
);
|
||||
builder.addText('A AAAAA AAA');
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
test('not laid out', () {
|
||||
final Paragraph paragraph = paragraphForBoundsTest(TextAlign.start);
|
||||
underTest.drawParagraph(paragraph, Offset.zero);
|
||||
underTest.endRecording();
|
||||
expect(underTest.pictureBounds, Rect.zero);
|
||||
});
|
||||
|
||||
test('finite width', () {
|
||||
final Paragraph paragraph = paragraphForBoundsTest(TextAlign.start);
|
||||
paragraph.layout(const ParagraphConstraints(width: 110));
|
||||
underTest.drawParagraph(paragraph, Offset.zero);
|
||||
underTest.endRecording();
|
||||
expect(paragraph.width, 110);
|
||||
expect(paragraph.height, 60);
|
||||
expect(underTest.pictureBounds, const Rect.fromLTRB(0, 0, 100, 60));
|
||||
});
|
||||
|
||||
test('finite width center-aligned', () {
|
||||
final Paragraph paragraph = paragraphForBoundsTest(TextAlign.center);
|
||||
paragraph.layout(const ParagraphConstraints(width: 110));
|
||||
underTest.drawParagraph(paragraph, Offset.zero);
|
||||
underTest.endRecording();
|
||||
expect(paragraph.width, 110);
|
||||
expect(paragraph.height, 60);
|
||||
expect(underTest.pictureBounds, const Rect.fromLTRB(5, 0, 105, 60));
|
||||
});
|
||||
|
||||
test('infinite width', () {
|
||||
final Paragraph paragraph = paragraphForBoundsTest(TextAlign.start);
|
||||
paragraph.layout(const ParagraphConstraints(width: double.infinity));
|
||||
underTest.drawParagraph(paragraph, Offset.zero);
|
||||
underTest.endRecording();
|
||||
expect(paragraph.width, double.infinity);
|
||||
expect(paragraph.height, 20);
|
||||
expect(underTest.pictureBounds, const Rect.fromLTRB(0, 0, 220, 20));
|
||||
});
|
||||
});
|
||||
|
||||
group('drawDRRect', () {
|
||||
final RRect rrect = RRect.fromLTRBR(10, 10, 50, 50, const Radius.circular(3));
|
||||
final SurfacePaint somePaint = SurfacePaint()..color = const Color(0xFFFF0000);
|
||||
|
||||
test('Happy case', () {
|
||||
underTest.drawDRRect(rrect, rrect.deflate(1), somePaint);
|
||||
underTest.endRecording();
|
||||
underTest.apply(mockCanvas, screenRect);
|
||||
|
||||
_expectDrawDRRectCall(mockCanvas, <String, dynamic>{
|
||||
'path':
|
||||
'Path('
|
||||
'MoveTo(${10.0}, ${47.0}) '
|
||||
'LineTo(${10.0}, ${13.0}) '
|
||||
'Conic(${10.0}, ${10.0}, ${10.0}, ${13.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${47.0}, ${10.0}) '
|
||||
'Conic(${50.0}, ${10.0}, ${10.0}, ${50.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${50.0}, ${47.0}) '
|
||||
'Conic(${50.0}, ${50.0}, ${50.0}, ${47.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${13.0}, ${50.0}) '
|
||||
'Conic(${10.0}, ${50.0}, ${50.0}, ${10.0}, w = ${0.7071067690849304}) '
|
||||
'Close() '
|
||||
'MoveTo(${11.0}, ${47.0}) '
|
||||
'LineTo(${11.0}, ${13.0}) '
|
||||
'Conic(${11.0}, ${11.0}, ${11.0}, ${13.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${47.0}, ${11.0}) '
|
||||
'Conic(${49.0}, ${11.0}, ${11.0}, ${49.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${49.0}, ${47.0}) '
|
||||
'Conic(${49.0}, ${49.0}, ${49.0}, ${47.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${13.0}, ${49.0}) '
|
||||
'Conic(${11.0}, ${49.0}, ${49.0}, ${11.0}, w = ${0.7071067690849304}) '
|
||||
'Close()'
|
||||
')',
|
||||
'paint': somePaint.paintData,
|
||||
});
|
||||
});
|
||||
|
||||
test('Inner RRect > Outer RRect', () {
|
||||
underTest.drawDRRect(rrect, rrect.inflate(1), somePaint);
|
||||
underTest.endRecording();
|
||||
underTest.apply(mockCanvas, screenRect);
|
||||
// Expect nothing to be called
|
||||
expect(mockCanvas.methodCallLog.length, equals(1));
|
||||
expect(mockCanvas.methodCallLog.single.methodName, 'endOfPaint');
|
||||
});
|
||||
|
||||
test('Inner RRect not completely inside Outer RRect', () {
|
||||
underTest.drawDRRect(rrect, rrect.deflate(1).shift(const Offset(0.0, 10)), somePaint);
|
||||
underTest.endRecording();
|
||||
underTest.apply(mockCanvas, screenRect);
|
||||
// Expect nothing to be called
|
||||
expect(mockCanvas.methodCallLog.length, equals(1));
|
||||
expect(mockCanvas.methodCallLog.single.methodName, 'endOfPaint');
|
||||
});
|
||||
|
||||
test('Inner RRect same as Outer RRect', () {
|
||||
underTest.drawDRRect(rrect, rrect, somePaint);
|
||||
underTest.endRecording();
|
||||
underTest.apply(mockCanvas, screenRect);
|
||||
// Expect nothing to be called
|
||||
expect(mockCanvas.methodCallLog.length, equals(1));
|
||||
expect(mockCanvas.methodCallLog.single.methodName, 'endOfPaint');
|
||||
});
|
||||
|
||||
test('deflated corners in inner RRect get passed through to draw', () {
|
||||
// This comes from github issue #40728
|
||||
final RRect outer = RRect.fromRectAndCorners(
|
||||
const Rect.fromLTWH(0, 0, 88, 48),
|
||||
topLeft: const Radius.circular(6),
|
||||
bottomLeft: const Radius.circular(6),
|
||||
);
|
||||
final RRect inner = outer.deflate(1);
|
||||
|
||||
expect(inner.brRadius, equals(Radius.zero));
|
||||
expect(inner.trRadius, equals(Radius.zero));
|
||||
|
||||
underTest.drawDRRect(outer, inner, somePaint);
|
||||
underTest.endRecording();
|
||||
underTest.apply(mockCanvas, screenRect);
|
||||
|
||||
// Expect to draw, even when inner has negative radii (which get ignored by canvas)
|
||||
_expectDrawDRRectCall(mockCanvas, <String, dynamic>{
|
||||
'path':
|
||||
'Path('
|
||||
'MoveTo(${0.0}, ${42.0}) '
|
||||
'LineTo(${0.0}, ${6.0}) '
|
||||
'Conic(${0.0}, ${0.0}, ${0.0}, ${6.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${88.0}, ${0.0}) '
|
||||
'Conic(${88.0}, ${0.0}, ${0.0}, ${88.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${88.0}, ${48.0}) '
|
||||
'Conic(${88.0}, ${48.0}, ${48.0}, ${88.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${6.0}, ${48.0}) '
|
||||
'Conic(${0.0}, ${48.0}, ${48.0}, ${0.0}, w = ${0.7071067690849304}) '
|
||||
'Close() '
|
||||
'MoveTo(${1.0}, ${42.0}) '
|
||||
'LineTo(${1.0}, ${6.0}) '
|
||||
'Conic(${1.0}, ${1.0}, ${1.0}, ${6.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${87.0}, ${1.0}) '
|
||||
'Conic(${87.0}, ${1.0}, ${1.0}, ${87.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${87.0}, ${47.0}) '
|
||||
'Conic(${87.0}, ${47.0}, ${47.0}, ${87.0}, w = ${0.7071067690849304}) '
|
||||
'LineTo(${6.0}, ${47.0}) '
|
||||
'Conic(${1.0}, ${47.0}, ${47.0}, ${1.0}, w = ${0.7071067690849304}) '
|
||||
'Close()'
|
||||
')',
|
||||
'paint': somePaint.paintData,
|
||||
});
|
||||
});
|
||||
|
||||
test('preserve old golden test behavior', () {
|
||||
final RRect outer = RRect.fromRectAndCorners(const Rect.fromLTRB(10, 20, 30, 40));
|
||||
final RRect inner = RRect.fromRectAndCorners(const Rect.fromLTRB(12, 22, 28, 38));
|
||||
|
||||
underTest.drawDRRect(outer, inner, somePaint);
|
||||
underTest.endRecording();
|
||||
underTest.apply(mockCanvas, screenRect);
|
||||
|
||||
_expectDrawDRRectCall(mockCanvas, <String, dynamic>{
|
||||
'path':
|
||||
'Path('
|
||||
'MoveTo(${10.0}, ${20.0}) '
|
||||
'LineTo(${30.0}, ${20.0}) '
|
||||
'LineTo(${30.0}, ${40.0}) '
|
||||
'LineTo(${10.0}, ${40.0}) '
|
||||
'Close() '
|
||||
'MoveTo(${12.0}, ${22.0}) '
|
||||
'LineTo(${28.0}, ${22.0}) '
|
||||
'LineTo(${28.0}, ${38.0}) '
|
||||
'LineTo(${12.0}, ${38.0}) '
|
||||
'Close()'
|
||||
')',
|
||||
'paint': somePaint.paintData,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Filters out paint commands outside the clip rect', () {
|
||||
// Outside to the left
|
||||
underTest.drawRect(const Rect.fromLTWH(0.0, 20.0, 10.0, 10.0), SurfacePaint());
|
||||
|
||||
// Outside above
|
||||
underTest.drawRect(const Rect.fromLTWH(20.0, 0.0, 10.0, 10.0), SurfacePaint());
|
||||
|
||||
// Visible
|
||||
underTest.drawRect(const Rect.fromLTWH(20.0, 20.0, 10.0, 10.0), SurfacePaint());
|
||||
|
||||
// Inside the layer clip rect but zero-size
|
||||
underTest.drawRect(const Rect.fromLTRB(20.0, 20.0, 30.0, 20.0), SurfacePaint());
|
||||
|
||||
// Inside the layer clip but clipped out by a canvas clip
|
||||
underTest.save();
|
||||
underTest.clipRect(const Rect.fromLTWH(0, 0, 10, 10), ClipOp.intersect);
|
||||
underTest.drawRect(const Rect.fromLTWH(20.0, 20.0, 10.0, 10.0), SurfacePaint());
|
||||
underTest.restore();
|
||||
|
||||
// Outside to the right
|
||||
underTest.drawRect(const Rect.fromLTWH(40.0, 20.0, 10.0, 10.0), SurfacePaint());
|
||||
|
||||
// Outside below
|
||||
underTest.drawRect(const Rect.fromLTWH(20.0, 40.0, 10.0, 10.0), SurfacePaint());
|
||||
|
||||
underTest.endRecording();
|
||||
|
||||
expect(underTest.debugPaintCommands, hasLength(10));
|
||||
final PaintDrawRect outsideLeft = underTest.debugPaintCommands[0] as PaintDrawRect;
|
||||
expect(outsideLeft.isClippedOut, isFalse);
|
||||
expect(outsideLeft.leftBound, 0);
|
||||
expect(outsideLeft.topBound, 20);
|
||||
expect(outsideLeft.rightBound, 10);
|
||||
expect(outsideLeft.bottomBound, 30);
|
||||
|
||||
final PaintDrawRect outsideAbove = underTest.debugPaintCommands[1] as PaintDrawRect;
|
||||
expect(outsideAbove.isClippedOut, isFalse);
|
||||
|
||||
final PaintDrawRect visible = underTest.debugPaintCommands[2] as PaintDrawRect;
|
||||
expect(visible.isClippedOut, isFalse);
|
||||
|
||||
final PaintDrawRect zeroSize = underTest.debugPaintCommands[3] as PaintDrawRect;
|
||||
expect(zeroSize.isClippedOut, isTrue);
|
||||
|
||||
expect(underTest.debugPaintCommands[4], isA<PaintSave>());
|
||||
|
||||
final PaintClipRect clip = underTest.debugPaintCommands[5] as PaintClipRect;
|
||||
expect(clip.isClippedOut, isFalse);
|
||||
|
||||
final PaintDrawRect clippedOut = underTest.debugPaintCommands[6] as PaintDrawRect;
|
||||
expect(clippedOut.isClippedOut, isTrue);
|
||||
|
||||
expect(underTest.debugPaintCommands[7], isA<PaintRestore>());
|
||||
|
||||
final PaintDrawRect outsideRight = underTest.debugPaintCommands[8] as PaintDrawRect;
|
||||
expect(outsideRight.isClippedOut, isFalse);
|
||||
|
||||
final PaintDrawRect outsideBelow = underTest.debugPaintCommands[9] as PaintDrawRect;
|
||||
expect(outsideBelow.isClippedOut, isFalse);
|
||||
|
||||
// Give it the entire screen so everything paints.
|
||||
underTest.apply(mockCanvas, screenRect);
|
||||
expect(mockCanvas.methodCallLog, hasLength(11));
|
||||
expect(mockCanvas.methodCallLog[0].methodName, 'drawRect');
|
||||
expect(mockCanvas.methodCallLog[1].methodName, 'drawRect');
|
||||
expect(mockCanvas.methodCallLog[2].methodName, 'drawRect');
|
||||
expect(mockCanvas.methodCallLog[3].methodName, 'drawRect');
|
||||
expect(mockCanvas.methodCallLog[4].methodName, 'save');
|
||||
expect(mockCanvas.methodCallLog[5].methodName, 'clipRect');
|
||||
expect(mockCanvas.methodCallLog[6].methodName, 'drawRect');
|
||||
expect(mockCanvas.methodCallLog[7].methodName, 'restore');
|
||||
expect(mockCanvas.methodCallLog[8].methodName, 'drawRect');
|
||||
expect(mockCanvas.methodCallLog[9].methodName, 'drawRect');
|
||||
expect(mockCanvas.methodCallLog[10].methodName, 'endOfPaint');
|
||||
|
||||
// Clip out a middle region that only contains 'drawRect'
|
||||
mockCanvas.methodCallLog.clear();
|
||||
underTest.apply(mockCanvas, const Rect.fromLTRB(15, 15, 35, 35));
|
||||
expect(mockCanvas.methodCallLog, hasLength(4));
|
||||
expect(mockCanvas.methodCallLog[0].methodName, 'drawRect');
|
||||
expect(mockCanvas.methodCallLog[1].methodName, 'save');
|
||||
expect(mockCanvas.methodCallLog[2].methodName, 'restore');
|
||||
expect(mockCanvas.methodCallLog[3].methodName, 'endOfPaint');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/61697.
|
||||
test('Allows restore calls after recording has ended', () {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 200, 400));
|
||||
rc.endRecording();
|
||||
// Should not throw exception on restore.
|
||||
expect(() => rc.restore(), returnsNormally);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/61697.
|
||||
test('Allows restore calls even if recording is not ended', () {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 200, 400));
|
||||
// Should not throw exception on restore.
|
||||
expect(() => rc.restore(), returnsNormally);
|
||||
});
|
||||
}
|
||||
|
||||
// Expect a drawDRRect call to be registered in the mock call log, with the expectedArguments
|
||||
void _expectDrawDRRectCall(MockEngineCanvas mock, Map<String, dynamic> expectedArguments) {
|
||||
expect(mock.methodCallLog.length, equals(2));
|
||||
final MockCanvasCall mockCall = mock.methodCallLog[0];
|
||||
expect(mockCall.methodName, equals('drawPath'));
|
||||
final Map<String, dynamic> argMap = mockCall.arguments as Map<String, dynamic>;
|
||||
final Map<String, dynamic> argContents = <String, dynamic>{};
|
||||
argMap.forEach((String key, dynamic value) {
|
||||
argContents[key] = value is SurfacePath ? value.toString() : value;
|
||||
});
|
||||
expect(argContents, equals(expectedArguments));
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// 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_web/src/ui_web.dart' as ui_web;
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
final DomElement hostElement = createDomHTMLDivElement();
|
||||
late DomManager domManager;
|
||||
late ResourceManager resourceManager;
|
||||
|
||||
setUp(() {
|
||||
domManager = DomManager(devicePixelRatio: 3);
|
||||
hostElement.appendChild(domManager.rootElement);
|
||||
resourceManager = ResourceManager(domManager);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
hostElement.clearChildren();
|
||||
});
|
||||
|
||||
test('prepends resources host as sibling to root element (webkit)', () {
|
||||
ui_web.browser.debugBrowserEngineOverride = ui_web.BrowserEngine.webkit;
|
||||
|
||||
// Resource host hasn't been inserted yet.
|
||||
expect(
|
||||
hostElement.children.map((DomElement e) => e.tagName.toLowerCase()),
|
||||
isNot(contains(ResourceManager.resourcesHostTagName.toLowerCase())),
|
||||
);
|
||||
|
||||
final List<DomElement> resources = <DomElement>[
|
||||
createDomHTMLDivElement()..setAttribute('test-resource', 'r1'),
|
||||
createDomHTMLDivElement()..setAttribute('test-resource', 'r2'),
|
||||
createDomHTMLDivElement()..setAttribute('test-resource', 'r3'),
|
||||
];
|
||||
resources.forEach(resourceManager.addResource);
|
||||
|
||||
final DomElement resourcesHost = hostElement.firstElementChild!;
|
||||
expect(resourcesHost.tagName.toLowerCase(), ResourceManager.resourcesHostTagName.toLowerCase());
|
||||
// Make sure the resources were correctly inserted into the host.
|
||||
expect(resourcesHost.children, resources);
|
||||
|
||||
ui_web.browser.debugBrowserEngineOverride = null;
|
||||
});
|
||||
|
||||
test('prepends resources host inside the shadow root (non-webkit)', () {
|
||||
ui_web.browser.debugBrowserEngineOverride = ui_web.BrowserEngine.blink;
|
||||
|
||||
// Resource host hasn't been inserted yet.
|
||||
expect(
|
||||
hostElement.children.map((DomElement e) => e.tagName.toLowerCase()),
|
||||
isNot(contains(ResourceManager.resourcesHostTagName.toLowerCase())),
|
||||
);
|
||||
|
||||
final List<DomElement> resources = <DomElement>[
|
||||
createDomHTMLDivElement()..setAttribute('test-resource', 'r1'),
|
||||
createDomHTMLDivElement()..setAttribute('test-resource', 'r2'),
|
||||
createDomHTMLDivElement()..setAttribute('test-resource', 'r3'),
|
||||
];
|
||||
resources.forEach(resourceManager.addResource);
|
||||
|
||||
final DomElement resourcesHost = domManager.renderingHost.firstElementChild!;
|
||||
expect(resourcesHost.tagName.toLowerCase(), ResourceManager.resourcesHostTagName.toLowerCase());
|
||||
// Make sure the resources were correctly inserted into the host.
|
||||
expect(resourcesHost.children, resources);
|
||||
|
||||
ui_web.browser.debugBrowserEngineOverride = null;
|
||||
});
|
||||
|
||||
test('can remove resource', () {
|
||||
final DomHTMLDivElement resource = createDomHTMLDivElement();
|
||||
resourceManager.addResource(resource);
|
||||
final DomElement? resourceRoot = resource.parent;
|
||||
expect(resourceRoot, isNotNull);
|
||||
expect(resourceRoot!.childNodes.length, 1);
|
||||
resourceManager.removeResource(resource);
|
||||
expect(resourceRoot.childNodes.length, 0);
|
||||
});
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// 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 'dart:async';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
/// Commit a recording canvas to a bitmap, and compare with the expected.
|
||||
///
|
||||
/// [region] specifies the area of the canvas that will be included in the
|
||||
/// golden.
|
||||
///
|
||||
/// If [canvasRect] is omitted, it defaults to the value of [region].
|
||||
Future<void> canvasScreenshot(
|
||||
RecordingCanvas rc,
|
||||
String fileName, {
|
||||
ui.Rect region = const ui.Rect.fromLTWH(0, 0, 500, 500),
|
||||
ui.Rect? canvasRect,
|
||||
bool setupPerspective = false,
|
||||
}) async {
|
||||
canvasRect ??= region;
|
||||
final EngineCanvas engineCanvas = BitmapCanvas(canvasRect, RenderStrategy());
|
||||
|
||||
rc.endRecording();
|
||||
rc.apply(engineCanvas, region);
|
||||
|
||||
// Wrap in <flt-scene> so that our CSS selectors kick in.
|
||||
final DomElement sceneElement = createDomElement('flt-scene');
|
||||
if (isIosSafari) {
|
||||
// Shrink to fit on the iPhone screen.
|
||||
sceneElement.style.position = 'absolute';
|
||||
sceneElement.style.transformOrigin = '0 0 0';
|
||||
sceneElement.style.transform = 'scale(0.3)';
|
||||
}
|
||||
try {
|
||||
if (setupPerspective) {
|
||||
// iFrame disables perspective, set it explicitly for test.
|
||||
engineCanvas.rootElement.style.perspective = '400px';
|
||||
for (final DomElement element in engineCanvas.rootElement.querySelectorAll('div')) {
|
||||
element.style.perspective = '400px';
|
||||
}
|
||||
}
|
||||
sceneElement.append(engineCanvas.rootElement);
|
||||
domDocument.body!.append(sceneElement);
|
||||
await matchGoldenFile('$fileName.png', region: region);
|
||||
} finally {
|
||||
// The page is reused across tests, so remove the element after taking the
|
||||
// screenshot.
|
||||
sceneElement.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sceneScreenshot(
|
||||
SurfaceSceneBuilder sceneBuilder,
|
||||
String fileName, {
|
||||
ui.Rect region = const ui.Rect.fromLTWH(0, 0, 600, 800),
|
||||
}) async {
|
||||
DomElement? sceneElement;
|
||||
try {
|
||||
sceneElement = sceneBuilder.build().webOnlyRootElement;
|
||||
domDocument.body!.append(sceneElement!);
|
||||
await matchGoldenFile('$fileName.png', region: region);
|
||||
} finally {
|
||||
// The page is reused across tests, so remove the element after taking the
|
||||
// screenshot.
|
||||
sceneElement?.remove();
|
||||
}
|
||||
}
|
||||
@@ -1,921 +0,0 @@
|
||||
// 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 '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';
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
// TODO(yjbanov): unskip Firefox tests when Firefox implements WebGL in headless mode.
|
||||
// https://github.com/flutter/flutter/issues/86623
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const double screenWidth = 600.0;
|
||||
const double screenHeight = 800.0;
|
||||
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
const Rect region = Rect.fromLTWH(0, 0, 500, 240);
|
||||
|
||||
setUpUnitTests(withImplicitView: true);
|
||||
|
||||
test('Paints sweep gradient rectangles', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
final SurfacePaint borderPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const Color(0xFF000000);
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
GradientSweep sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
0,
|
||||
360.0 / 180.0 * math.pi,
|
||||
null,
|
||||
);
|
||||
|
||||
final GradientSweep sweepGradientRotated = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
0,
|
||||
360.0 / 180.0 * math.pi,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
// Gradient with default center.
|
||||
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with shifted center and rotation.
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()
|
||||
..shader = engineGradientToShader(
|
||||
sweepGradientRotated,
|
||||
Rect.fromLTWH(
|
||||
rectBounds.center.dx,
|
||||
rectBounds.top,
|
||||
rectBounds.width / 2,
|
||||
rectBounds.height,
|
||||
),
|
||||
),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with start/endangle.
|
||||
sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
math.pi / 6,
|
||||
3 * math.pi / 4,
|
||||
null,
|
||||
);
|
||||
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode repeat
|
||||
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.repeated,
|
||||
math.pi / 6,
|
||||
3 * math.pi / 4,
|
||||
null,
|
||||
);
|
||||
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode mirror
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.mirror,
|
||||
math.pi / 6,
|
||||
3 * math.pi / 4,
|
||||
null,
|
||||
);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(canvas, 'sweep_gradient_rect', canvasRect: screenRect, region: region);
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Paints sweep gradient ovals', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
final SurfacePaint borderPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const Color(0xFF000000);
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
final List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
GradientSweep sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
0,
|
||||
360.0 / 180.0 * math.pi,
|
||||
null,
|
||||
);
|
||||
|
||||
final GradientSweep sweepGradientRotated = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
0,
|
||||
360.0 / 180.0 * math.pi,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
// Gradient with default center.
|
||||
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawOval(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with shifted center and rotation.
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
canvas.drawOval(
|
||||
rectBounds,
|
||||
SurfacePaint()
|
||||
..shader = engineGradientToShader(
|
||||
sweepGradientRotated,
|
||||
Rect.fromLTWH(
|
||||
rectBounds.center.dx,
|
||||
rectBounds.top,
|
||||
rectBounds.width / 2,
|
||||
rectBounds.height,
|
||||
),
|
||||
),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with start/endangle.
|
||||
sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
math.pi / 6,
|
||||
3 * math.pi / 4,
|
||||
null,
|
||||
);
|
||||
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
canvas.drawOval(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode repeat
|
||||
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.repeated,
|
||||
math.pi / 6,
|
||||
3 * math.pi / 4,
|
||||
null,
|
||||
);
|
||||
|
||||
canvas.drawOval(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode mirror
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.mirror,
|
||||
math.pi / 6,
|
||||
3 * math.pi / 4,
|
||||
null,
|
||||
);
|
||||
canvas.drawOval(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(canvas, 'sweep_gradient_oval', canvasRect: screenRect, region: region);
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Paints sweep gradient paths', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
final SurfacePaint borderPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const Color(0xFF000000);
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
GradientSweep sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
0,
|
||||
360.0 / 180.0 * math.pi,
|
||||
null,
|
||||
);
|
||||
|
||||
final GradientSweep sweepGradientRotated = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
0,
|
||||
360.0 / 180.0 * math.pi,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
// Gradient with default center.
|
||||
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
Path path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with shifted center and rotation.
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..shader = engineGradientToShader(
|
||||
sweepGradientRotated,
|
||||
Rect.fromLTWH(
|
||||
rectBounds.center.dx,
|
||||
rectBounds.top,
|
||||
rectBounds.width / 2,
|
||||
rectBounds.height,
|
||||
),
|
||||
),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with start/endangle.
|
||||
sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
math.pi / 6,
|
||||
3 * math.pi / 4,
|
||||
null,
|
||||
);
|
||||
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode repeat
|
||||
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.repeated,
|
||||
math.pi / 6,
|
||||
3 * math.pi / 4,
|
||||
null,
|
||||
);
|
||||
|
||||
path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode mirror
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.mirror,
|
||||
math.pi / 6,
|
||||
3 * math.pi / 4,
|
||||
null,
|
||||
);
|
||||
path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(canvas, 'sweep_gradient_path', canvasRect: screenRect, region: region);
|
||||
}, skip: isFirefox);
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/74137.
|
||||
test('Paints rotated and shifted linear gradient', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
final SurfacePaint borderPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const Color(0xFF000000);
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
GradientLinear linearGradient = GradientLinear(
|
||||
const Offset(50, 50),
|
||||
const Offset(200, 130),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
// Gradient with default center.
|
||||
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineLinearGradientToShader(linearGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode repeat
|
||||
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
linearGradient = GradientLinear(
|
||||
const Offset(50, 50),
|
||||
const Offset(200, 130),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.repeated,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineLinearGradientToShader(linearGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(
|
||||
canvas,
|
||||
'linear_gradient_rect_shifted',
|
||||
canvasRect: screenRect,
|
||||
region: region,
|
||||
);
|
||||
}, skip: isFirefox);
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/82748.
|
||||
test('Paints gradient with gradient stop outside range', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
final SurfacePaint borderPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const Color(0xFF000000);
|
||||
|
||||
const List<Color> colors = <Color>[Color(0xFF000000), Color(0xFFFF3C38)];
|
||||
const List<double> stops = <double>[0.0, 10.0];
|
||||
|
||||
final GradientLinear linearGradient = GradientLinear(
|
||||
const Offset(50, 50),
|
||||
const Offset(200, 130),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
// Gradient with default center.
|
||||
const Rect rectBounds = Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineLinearGradientToShader(linearGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
canvas.restore();
|
||||
|
||||
final EngineCanvas engineCanvas = BitmapCanvas(screenRect, RenderStrategy());
|
||||
canvas.endRecording();
|
||||
canvas.apply(engineCanvas, screenRect);
|
||||
}, skip: isFirefox);
|
||||
|
||||
test("Creating lots of gradients doesn't create too many webgl contexts", () async {
|
||||
final DomCanvasElement sideCanvas = createDomCanvasElement(width: 5, height: 5);
|
||||
final DomCanvasRenderingContextWebGl? context =
|
||||
sideCanvas.getContext('webgl') as DomCanvasRenderingContextWebGl?;
|
||||
expect(context, isNotNull);
|
||||
|
||||
final EngineCanvas engineCanvas = BitmapCanvas(
|
||||
const Rect.fromLTRB(0, 0, 100, 100),
|
||||
RenderStrategy(),
|
||||
);
|
||||
for (double x = 0; x < 100; x += 10) {
|
||||
for (double y = 0; y < 100; y += 10) {
|
||||
const List<Color> colors = <Color>[Color(0xFFFF0000), Color(0xFF0000FF)];
|
||||
|
||||
final GradientLinear linearGradient = GradientLinear(
|
||||
Offset.zero,
|
||||
const Offset(10, 10),
|
||||
colors,
|
||||
null,
|
||||
TileMode.clamp,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
engineCanvas.drawRect(
|
||||
Rect.fromLTWH(x, y, 10, 10),
|
||||
SurfacePaintData()..shader = linearGradient,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
expect(context!.isContextLost(), isFalse);
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Paints clamped, rotated and shifted linear gradient', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
final SurfacePaint borderPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const Color(0xFF000000);
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
GradientLinear linearGradient = GradientLinear(
|
||||
const Offset(50, 50),
|
||||
const Offset(200, 130),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
// Gradient with default center.
|
||||
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineLinearGradientToShader(linearGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode repeat
|
||||
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
linearGradient = GradientLinear(
|
||||
const Offset(50, 50),
|
||||
const Offset(200, 130),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineLinearGradientToShader(linearGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(
|
||||
canvas,
|
||||
'linear_gradient_rect_clamp_rotated',
|
||||
canvasRect: screenRect,
|
||||
region: region,
|
||||
);
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Paints linear gradient properly when within svg context', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 240));
|
||||
canvas.save();
|
||||
|
||||
canvas.renderStrategy.isInsideSvgFilterTree = true;
|
||||
|
||||
final SurfacePaint borderPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const Color(0xFF000000);
|
||||
|
||||
const List<Color> colors = <Color>[Color(0xFFFF0000), Color(0xFF0000FF)];
|
||||
|
||||
final GradientLinear linearGradient = GradientLinear(
|
||||
const Offset(125, 75),
|
||||
const Offset(175, 125),
|
||||
colors,
|
||||
null,
|
||||
TileMode.clamp,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 100;
|
||||
// Gradient with default center.
|
||||
const Rect rectBounds = Rect.fromLTWH(100, 50, kBoxWidth, kBoxHeight);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineLinearGradientToShader(linearGradient, rectBounds),
|
||||
);
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(
|
||||
canvas,
|
||||
'linear_gradient_in_svg_context',
|
||||
canvasRect: screenRect,
|
||||
region: region,
|
||||
);
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Paints transformed linear gradient', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..translate(50, 50)
|
||||
..scale(0.3, 0.7)
|
||||
..rotateZ(0.5);
|
||||
|
||||
final GradientLinear linearGradient = GradientLinear(
|
||||
const Offset(5, 5),
|
||||
const Offset(200, 130),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
transform.storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
|
||||
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineLinearGradientToShader(linearGradient, rectBounds),
|
||||
);
|
||||
|
||||
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
canvas.drawOval(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineLinearGradientToShader(linearGradient, rectBounds),
|
||||
);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(
|
||||
canvas,
|
||||
'linear_gradient_clamp_transformed',
|
||||
canvasRect: screenRect,
|
||||
region: region,
|
||||
);
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Paints transformed sweep gradient', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..translate(100, 150)
|
||||
..scale(0.3, 0.7)
|
||||
..rotateZ(0.5);
|
||||
|
||||
final GradientSweep sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
0.0,
|
||||
2 * math.pi,
|
||||
transform.storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
|
||||
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
|
||||
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
canvas.drawOval(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds),
|
||||
);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(
|
||||
canvas,
|
||||
'sweep_gradient_clamp_transformed',
|
||||
canvasRect: screenRect,
|
||||
region: region,
|
||||
);
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Paints transformed radial gradient', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..translate(50, 50)
|
||||
..scale(0.3, 0.7)
|
||||
..rotateZ(0.5);
|
||||
|
||||
final GradientRadial radialGradient = GradientRadial(
|
||||
const Offset(0.5, 0.5),
|
||||
400,
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
transform.storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
|
||||
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawRect(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineRadialGradientToShader(radialGradient, rectBounds),
|
||||
);
|
||||
|
||||
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
canvas.drawOval(
|
||||
rectBounds,
|
||||
SurfacePaint()..shader = engineRadialGradientToShader(radialGradient, rectBounds),
|
||||
);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(
|
||||
canvas,
|
||||
'radial_gradient_clamp_transformed',
|
||||
canvasRect: screenRect,
|
||||
region: region,
|
||||
);
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Paints two gradient with same width and different height', () async {
|
||||
final RecordingCanvas canvas = RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
final Matrix4 transform =
|
||||
Matrix4.identity()
|
||||
..translate(100, 150)
|
||||
..scale(0.3, 0.7)
|
||||
..rotateZ(0.5);
|
||||
|
||||
final GradientSweep sweepGradient = GradientSweep(
|
||||
const Offset(0.5, 0.5),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
0.0,
|
||||
2 * math.pi,
|
||||
transform.storage,
|
||||
);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight1 = 40;
|
||||
const double kBoxHeight2 = 80;
|
||||
|
||||
const Rect rectBounds1 = Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight1);
|
||||
const Rect rectBounds2 = Rect.fromLTWH(10, 80, kBoxWidth, kBoxHeight2);
|
||||
canvas.drawRect(
|
||||
rectBounds1,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds1),
|
||||
);
|
||||
canvas.drawRect(
|
||||
rectBounds2,
|
||||
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds2),
|
||||
);
|
||||
|
||||
canvas.restore();
|
||||
await canvasScreenshot(
|
||||
canvas,
|
||||
'radial_gradient_double_item',
|
||||
canvasRect: screenRect,
|
||||
region: region,
|
||||
);
|
||||
}, skip: isFirefox);
|
||||
}
|
||||
|
||||
Shader engineGradientToShader(GradientSweep gradient, Rect rect) {
|
||||
return Gradient.sweep(
|
||||
Offset(
|
||||
rect.left + gradient.center.dx * rect.width,
|
||||
rect.top + gradient.center.dy * rect.height,
|
||||
),
|
||||
gradient.colors,
|
||||
gradient.colorStops,
|
||||
gradient.tileMode,
|
||||
gradient.startAngle,
|
||||
gradient.endAngle,
|
||||
gradient.matrix4 == null ? null : Float64List.fromList(gradient.matrix4!),
|
||||
);
|
||||
}
|
||||
|
||||
Shader engineLinearGradientToShader(GradientLinear gradient, Rect rect) {
|
||||
return Gradient.linear(
|
||||
gradient.from,
|
||||
gradient.to,
|
||||
gradient.colors,
|
||||
gradient.colorStops,
|
||||
gradient.tileMode,
|
||||
gradient.matrix4 == null ? null : Float64List.fromList(gradient.matrix4!.matrix),
|
||||
);
|
||||
}
|
||||
|
||||
Shader engineRadialGradientToShader(GradientRadial gradient, Rect rect) {
|
||||
return Gradient.radial(
|
||||
Offset(
|
||||
rect.left + gradient.center.dx * rect.width,
|
||||
rect.top + gradient.center.dy * rect.height,
|
||||
),
|
||||
gradient.radius,
|
||||
gradient.colors,
|
||||
gradient.colorStops,
|
||||
gradient.tileMode,
|
||||
gradient.matrix4 == null ? null : Float64List.fromList(gradient.matrix4!),
|
||||
);
|
||||
}
|
||||
|
||||
Path samplePathFromRect(Rect rectBounds) =>
|
||||
Path()
|
||||
..moveTo(rectBounds.center.dx, rectBounds.top)
|
||||
..lineTo(rectBounds.left, rectBounds.bottom)
|
||||
..quadraticBezierTo(
|
||||
rectBounds.center.dx + 20,
|
||||
rectBounds.bottom - 40,
|
||||
rectBounds.right,
|
||||
rectBounds.bottom,
|
||||
)
|
||||
..close();
|
||||
@@ -1,137 +0,0 @@
|
||||
// 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 'dart:js_util' as js_util;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' hide TextStyle;
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
// TODO(yjbanov): unskip Firefox tests when Firefox implements WebGL in headless mode.
|
||||
// https://github.com/flutter/flutter/issues/86623
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const double screenWidth = 400.0;
|
||||
const double screenHeight = 400.0;
|
||||
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
final HtmlImage testImage = createTestImage();
|
||||
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
void drawShapes(RecordingCanvas rc, SurfacePaint paint, Rect shaderRect) {
|
||||
/// Rect.
|
||||
rc.drawRect(shaderRect, paint);
|
||||
shaderRect = shaderRect.translate(100, 0);
|
||||
|
||||
/// Circle.
|
||||
rc.drawCircle(shaderRect.center, shaderRect.width / 2, paint);
|
||||
shaderRect = shaderRect.translate(110, 0);
|
||||
|
||||
/// Oval.
|
||||
rc.drawOval(
|
||||
Rect.fromLTWH(shaderRect.left, shaderRect.top, shaderRect.width, shaderRect.height / 2),
|
||||
paint,
|
||||
);
|
||||
shaderRect = shaderRect.translate(-210, 120);
|
||||
|
||||
/// Path.
|
||||
final Path path =
|
||||
Path()
|
||||
..moveTo(shaderRect.center.dx, shaderRect.top)
|
||||
..lineTo(shaderRect.right, shaderRect.bottom)
|
||||
..lineTo(shaderRect.left, shaderRect.bottom)
|
||||
..close();
|
||||
rc.drawPath(path, paint);
|
||||
shaderRect = shaderRect.translate(100, 0);
|
||||
|
||||
/// RRect.
|
||||
rc.drawRRect(RRect.fromRectXY(shaderRect, 10, 20), paint);
|
||||
shaderRect = shaderRect.translate(110, 0);
|
||||
|
||||
/// DRRect.
|
||||
rc.drawDRRect(
|
||||
RRect.fromRectXY(shaderRect, 20, 30),
|
||||
RRect.fromRectXY(shaderRect.deflate(24), 16, 24),
|
||||
paint,
|
||||
);
|
||||
shaderRect = shaderRect.translate(-200, 120);
|
||||
}
|
||||
|
||||
Future<void> testImageShader(TileMode tmx, TileMode tmy, String fileName) async {
|
||||
final RecordingCanvas rc = RecordingCanvas(
|
||||
const Rect.fromLTRB(0, 0, screenWidth, screenHeight),
|
||||
);
|
||||
//Rect shaderRect = const Rect.fromLTRB(20, 20, 100, 100);
|
||||
const Rect shaderRect = Rect.fromLTRB(0, 0, 100, 100);
|
||||
final SurfacePaint paint = Paint() as SurfacePaint;
|
||||
paint.shader = ImageShader(
|
||||
testImage,
|
||||
tmx,
|
||||
tmy,
|
||||
Matrix4.identity().toFloat64(),
|
||||
filterQuality: FilterQuality.high,
|
||||
);
|
||||
|
||||
drawShapes(rc, paint, shaderRect);
|
||||
|
||||
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
|
||||
await canvasScreenshot(rc, fileName, region: screenRect);
|
||||
}
|
||||
|
||||
test('Should draw with tiled imageshader.', () async {
|
||||
await testImageShader(TileMode.repeated, TileMode.repeated, 'image_shader_tiled');
|
||||
});
|
||||
|
||||
test('Should draw with horizontally mirrored imageshader.', () async {
|
||||
await testImageShader(TileMode.mirror, TileMode.repeated, 'image_shader_horiz_mirror');
|
||||
});
|
||||
|
||||
test('Should draw with vertically mirrored imageshader.', () async {
|
||||
await testImageShader(TileMode.repeated, TileMode.mirror, 'image_shader_vert_mirror');
|
||||
});
|
||||
|
||||
test('Should draw with mirrored imageshader.', () async {
|
||||
await testImageShader(TileMode.mirror, TileMode.mirror, 'image_shader_mirror');
|
||||
});
|
||||
|
||||
test('Should draw with horizontal clamp imageshader.', () async {
|
||||
await testImageShader(TileMode.clamp, TileMode.repeated, 'image_shader_clamp_horiz');
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Should draw with vertical clamp imageshader.', () async {
|
||||
await testImageShader(TileMode.repeated, TileMode.clamp, 'image_shader_clamp_vertical');
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Should draw with clamp imageshader.', () async {
|
||||
await testImageShader(TileMode.clamp, TileMode.clamp, 'image_shader_clamp');
|
||||
}, skip: isFirefox);
|
||||
}
|
||||
|
||||
HtmlImage createTestImage() {
|
||||
const int width = 16;
|
||||
const int width2 = width ~/ 2;
|
||||
const int height = 16;
|
||||
final DomCanvasElement canvas = createDomCanvasElement(width: width, height: height);
|
||||
final DomCanvasRenderingContext2D ctx = canvas.context2D;
|
||||
ctx.fillStyle = '#E04040';
|
||||
ctx.fillRect(0, 0, width2, width2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#40E080';
|
||||
ctx.fillRect(width2, 0, width2, width2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#2040E0';
|
||||
ctx.fillRect(width2, width2, width2, width2);
|
||||
ctx.fill();
|
||||
final DomHTMLImageElement imageElement = createDomHTMLImageElement();
|
||||
imageElement.src = js_util.callMethod<String>(canvas, 'toDataURL', <dynamic>[]);
|
||||
return HtmlImage(imageElement, width, height);
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
// 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 'dart:math' as math;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' hide TextStyle;
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
// TODO(yjbanov): unskip Firefox tests when Firefox implements WebGL in headless mode.
|
||||
// https://github.com/flutter/flutter/issues/86623
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
test('Should draw linear gradient using rectangle.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
const Rect shaderRect = Rect.fromLTRB(50, 50, 300, 300);
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..shader = Gradient.linear(
|
||||
Offset(shaderRect.left, shaderRect.top),
|
||||
Offset(shaderRect.right, shaderRect.bottom),
|
||||
const <Color>[Color(0xFFcfdfd2), Color(0xFF042a85)],
|
||||
);
|
||||
rc.drawRect(shaderRect, paint);
|
||||
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
|
||||
await canvasScreenshot(rc, 'linear_gradient_rect');
|
||||
});
|
||||
|
||||
test('Should blend linear gradient with alpha channel correctly.', () async {
|
||||
const Rect canvasRect = Rect.fromLTRB(0, 0, 500, 500);
|
||||
final RecordingCanvas rc = RecordingCanvas(canvasRect);
|
||||
final SurfacePaint backgroundPaint =
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xFFFF0000);
|
||||
rc.drawRect(canvasRect, backgroundPaint);
|
||||
|
||||
const Rect shaderRect = Rect.fromLTRB(50, 50, 300, 300);
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..shader = Gradient.linear(
|
||||
Offset(shaderRect.left, shaderRect.top),
|
||||
Offset(shaderRect.right, shaderRect.bottom),
|
||||
const <Color>[Color(0x00000000), Color(0xFF0000FF)],
|
||||
);
|
||||
rc.drawRect(shaderRect, paint);
|
||||
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
|
||||
await canvasScreenshot(rc, 'linear_gradient_rect_alpha');
|
||||
});
|
||||
|
||||
test('Should draw linear gradient with transform.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
final List<double> angles = <double>[0.0, 90.0, 180.0];
|
||||
double yOffset = 0;
|
||||
for (final double angle in angles) {
|
||||
final Rect shaderRect = Rect.fromLTWH(50, 50 + yOffset, 100, 100);
|
||||
final Matrix4 matrix = Matrix4.identity();
|
||||
matrix.translate(shaderRect.left, shaderRect.top);
|
||||
matrix.multiply(Matrix4.rotationZ((angle / 180) * math.pi));
|
||||
final Matrix4 post = Matrix4.identity();
|
||||
post.translate(-shaderRect.left, -shaderRect.top);
|
||||
matrix.multiply(post);
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..shader = Gradient.linear(
|
||||
Offset(shaderRect.left, shaderRect.top),
|
||||
Offset(shaderRect.right, shaderRect.bottom),
|
||||
const <Color>[Color(0xFFFF0000), Color(0xFF042a85)],
|
||||
null,
|
||||
TileMode.clamp,
|
||||
matrix.toFloat64(),
|
||||
);
|
||||
rc.drawRect(shaderRect, SurfacePaint()..color = const Color(0xFF000000));
|
||||
rc.drawOval(shaderRect, paint);
|
||||
yOffset += 120;
|
||||
}
|
||||
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
|
||||
await canvasScreenshot(rc, 'linear_gradient_oval_matrix');
|
||||
}, skip: isFirefox);
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/50010
|
||||
test('Should draw linear gradient using rounded rect.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
const Rect shaderRect = Rect.fromLTRB(50, 50, 300, 300);
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..shader = Gradient.linear(
|
||||
Offset(shaderRect.left, shaderRect.top),
|
||||
Offset(shaderRect.right, shaderRect.bottom),
|
||||
const <Color>[Color(0xFFcfdfd2), Color(0xFF042a85)],
|
||||
);
|
||||
rc.drawRRect(RRect.fromRectAndRadius(shaderRect, const Radius.circular(16)), paint);
|
||||
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
|
||||
await canvasScreenshot(rc, 'linear_gradient_rounded_rect');
|
||||
});
|
||||
|
||||
test('Should draw tiled repeated linear gradient with transform.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
final List<double> angles = <double>[0.0, 30.0, 210.0];
|
||||
double yOffset = 0;
|
||||
for (final double angle in angles) {
|
||||
final Rect shaderRect = Rect.fromLTWH(50, 50 + yOffset, 100, 100);
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..shader = Gradient.linear(
|
||||
Offset(shaderRect.left, shaderRect.top),
|
||||
Offset(shaderRect.left + shaderRect.width / 2, shaderRect.top),
|
||||
const <Color>[Color(0xFFFF0000), Color(0xFF042a85)],
|
||||
null,
|
||||
TileMode.repeated,
|
||||
Matrix4.rotationZ((angle / 180) * math.pi).toFloat64(),
|
||||
);
|
||||
rc.drawRect(shaderRect, SurfacePaint()..color = const Color(0xFF000000));
|
||||
rc.drawOval(shaderRect, paint);
|
||||
yOffset += 120;
|
||||
}
|
||||
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
|
||||
await canvasScreenshot(rc, 'linear_gradient_tiled_repeated_rect');
|
||||
}, skip: isFirefox);
|
||||
|
||||
test('Should draw tiled mirrored linear gradient with transform.', () async {
|
||||
final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
|
||||
final List<double> angles = <double>[0.0, 30.0, 210.0];
|
||||
double yOffset = 0;
|
||||
for (final double angle in angles) {
|
||||
final Rect shaderRect = Rect.fromLTWH(50, 50 + yOffset, 100, 100);
|
||||
final SurfacePaint paint =
|
||||
SurfacePaint()
|
||||
..shader = Gradient.linear(
|
||||
Offset(shaderRect.left, shaderRect.top),
|
||||
Offset(shaderRect.left + shaderRect.width / 2, shaderRect.top),
|
||||
const <Color>[Color(0xFFFF0000), Color(0xFF042a85)],
|
||||
null,
|
||||
TileMode.mirror,
|
||||
Matrix4.rotationZ((angle / 180) * math.pi).toFloat64(),
|
||||
);
|
||||
rc.drawRect(shaderRect, SurfacePaint()..color = const Color(0xFF000000));
|
||||
rc.drawOval(shaderRect, paint);
|
||||
yOffset += 120;
|
||||
}
|
||||
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
|
||||
await canvasScreenshot(rc, 'linear_gradient_tiled_mirrored_rect');
|
||||
}, skip: isFirefox);
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// 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' hide TextStyle;
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../screenshot.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
Future<void> testGradient(
|
||||
String fileName,
|
||||
Shader shader, {
|
||||
Rect paintRect = const Rect.fromLTRB(50, 50, 300, 300),
|
||||
Rect shaderRect = const Rect.fromLTRB(50, 50, 300, 300),
|
||||
Rect region = const Rect.fromLTWH(0, 0, 500, 500),
|
||||
}) async {
|
||||
final RecordingCanvas rc = RecordingCanvas(region);
|
||||
final SurfacePaint paint = SurfacePaint()..shader = shader;
|
||||
final Path path = Path();
|
||||
path.addRect(paintRect);
|
||||
rc.drawPath(path, paint);
|
||||
await canvasScreenshot(rc, fileName, region: region);
|
||||
}
|
||||
|
||||
test('Should draw centered radial gradient.', () async {
|
||||
const Rect shaderRect = Rect.fromLTRB(50, 50, 300, 300);
|
||||
await testGradient(
|
||||
'radial_gradient_centered',
|
||||
Gradient.radial(
|
||||
Offset((shaderRect.left + shaderRect.right) / 2, (shaderRect.top + shaderRect.bottom) / 2),
|
||||
shaderRect.width / 2,
|
||||
<Color>[const Color.fromARGB(255, 0, 0, 0), const Color.fromARGB(255, 0, 0, 255)],
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('Should draw right bottom centered radial gradient.', () async {
|
||||
const Rect shaderRect = Rect.fromLTRB(50, 50, 300, 300);
|
||||
await testGradient(
|
||||
'radial_gradient_right_bottom',
|
||||
Gradient.radial(Offset(shaderRect.right, shaderRect.bottom), shaderRect.width / 2, <Color>[
|
||||
const Color.fromARGB(255, 0, 0, 0),
|
||||
const Color.fromARGB(255, 0, 0, 255),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('Should draw with radial gradient with TileMode.clamp.', () async {
|
||||
const Rect shaderRect = Rect.fromLTRB(50, 50, 100, 100);
|
||||
await testGradient(
|
||||
'radial_gradient_tilemode_clamp',
|
||||
Gradient.radial(
|
||||
Offset((shaderRect.left + shaderRect.right) / 2, (shaderRect.top + shaderRect.bottom) / 2),
|
||||
shaderRect.width / 2,
|
||||
<Color>[const Color.fromARGB(255, 0, 0, 0), const Color.fromARGB(255, 0, 0, 255)],
|
||||
<double>[0.0, 1.0],
|
||||
),
|
||||
shaderRect: shaderRect,
|
||||
);
|
||||
});
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
const List<double> colorStops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
test(
|
||||
'Should draw with radial gradient with TileMode.repeated.',
|
||||
() async {
|
||||
const Rect shaderRect = Rect.fromLTRB(50, 50, 100, 100);
|
||||
await testGradient(
|
||||
'radial_gradient_tilemode_repeated',
|
||||
Gradient.radial(
|
||||
Offset(
|
||||
(shaderRect.left + shaderRect.right) / 2,
|
||||
(shaderRect.top + shaderRect.bottom) / 2,
|
||||
),
|
||||
shaderRect.width / 2,
|
||||
colors,
|
||||
colorStops,
|
||||
TileMode.repeated,
|
||||
),
|
||||
shaderRect: shaderRect,
|
||||
region: const Rect.fromLTWH(0, 0, 600, 800),
|
||||
);
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
|
||||
test(
|
||||
'Should draw with radial gradient with TileMode.mirrored.',
|
||||
() async {
|
||||
const Rect shaderRect = Rect.fromLTRB(50, 50, 100, 100);
|
||||
await testGradient(
|
||||
'radial_gradient_tilemode_mirror',
|
||||
Gradient.radial(
|
||||
Offset(
|
||||
(shaderRect.left + shaderRect.right) / 2,
|
||||
(shaderRect.top + shaderRect.bottom) / 2,
|
||||
),
|
||||
shaderRect.width / 2,
|
||||
colors,
|
||||
colorStops,
|
||||
TileMode.mirror,
|
||||
),
|
||||
shaderRect: shaderRect,
|
||||
region: const Rect.fromLTWH(0, 0, 600, 800),
|
||||
);
|
||||
},
|
||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/86623
|
||||
skip: isFirefox,
|
||||
);
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
// 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' hide BackdropFilterEngineLayer, ClipRectEngineLayer;
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
|
||||
/// To debug compositing failures on browsers, set this flag to true and run
|
||||
/// flutter run -d chrome --web-renderer=html
|
||||
/// test/golden_tests/engine/shader_mask_golden_test.dart --profile
|
||||
const bool debugTest = false;
|
||||
|
||||
DomElement get sceneHost =>
|
||||
EnginePlatformDispatcher.instance.implicitView!.dom.renderingHost.querySelector(
|
||||
DomManager.sceneHostTagName,
|
||||
)!;
|
||||
|
||||
Future<void> main() async {
|
||||
if (!debugTest) {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
} else {
|
||||
_renderCirclesScene(BlendMode.color);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(ferhat): unskip webkit tests once flakiness is resolved. See
|
||||
// https://github.com/flutter/flutter/issues/76713
|
||||
// TODO(yjbanov): unskip Firefox tests when Firefox implements WebGL in headless mode.
|
||||
// https://github.com/flutter/flutter/issues/86623
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(
|
||||
withImplicitView: true,
|
||||
emulateTesterEnvironment: false,
|
||||
setUpTestViewDimensions: false,
|
||||
);
|
||||
|
||||
setUpAll(() async {
|
||||
debugShowClipLayers = true;
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
for (final DomNode scene in sceneHost.querySelectorAll('flt-scene').cast<DomNode>()) {
|
||||
scene.remove();
|
||||
}
|
||||
initWebGl();
|
||||
});
|
||||
|
||||
/// Should render the picture unmodified.
|
||||
test('Renders shader mask with linear gradient BlendMode dst', () async {
|
||||
_renderCirclesScene(BlendMode.dst);
|
||||
await matchGoldenFile('shadermask_linear_dst.png', region: const Rect.fromLTWH(0, 0, 360, 200));
|
||||
}, skip: isSafari || isFirefox);
|
||||
|
||||
/// Should render the gradient only where circles have alpha channel.
|
||||
test('Renders shader mask with linear gradient BlendMode srcIn', () async {
|
||||
_renderCirclesScene(BlendMode.srcIn);
|
||||
await matchGoldenFile(
|
||||
'shadermask_linear_srcin.png',
|
||||
region: const Rect.fromLTWH(0, 0, 360, 200),
|
||||
);
|
||||
}, skip: isSafari || isFirefox);
|
||||
|
||||
test('Renders shader mask with linear gradient BlendMode color', () async {
|
||||
_renderCirclesScene(BlendMode.color);
|
||||
await matchGoldenFile(
|
||||
'shadermask_linear_color.png',
|
||||
region: const Rect.fromLTWH(0, 0, 360, 200),
|
||||
);
|
||||
}, skip: isSafari || isFirefox);
|
||||
|
||||
test('Renders shader mask with linear gradient BlendMode xor', () async {
|
||||
_renderCirclesScene(BlendMode.xor);
|
||||
await matchGoldenFile('shadermask_linear_xor.png', region: const Rect.fromLTWH(0, 0, 360, 200));
|
||||
}, skip: isSafari || isFirefox);
|
||||
|
||||
test('Renders shader mask with linear gradient BlendMode plus', () async {
|
||||
_renderCirclesScene(BlendMode.plus);
|
||||
await matchGoldenFile(
|
||||
'shadermask_linear_plus.png',
|
||||
region: const Rect.fromLTWH(0, 0, 360, 200),
|
||||
);
|
||||
}, skip: isSafari || isFirefox);
|
||||
|
||||
test('Renders shader mask with linear gradient BlendMode modulate', () async {
|
||||
_renderCirclesScene(BlendMode.modulate);
|
||||
await matchGoldenFile(
|
||||
'shadermask_linear_modulate.png',
|
||||
region: const Rect.fromLTWH(0, 0, 360, 200),
|
||||
);
|
||||
}, skip: isSafari || isFirefox);
|
||||
|
||||
test('Renders shader mask with linear gradient BlendMode overlay', () async {
|
||||
_renderCirclesScene(BlendMode.overlay);
|
||||
await matchGoldenFile(
|
||||
'shadermask_linear_overlay.png',
|
||||
region: const Rect.fromLTWH(0, 0, 360, 200),
|
||||
);
|
||||
}, skip: isSafari || isFirefox);
|
||||
|
||||
/// Should render the gradient opaque on top of content.
|
||||
test('Renders shader mask with linear gradient BlendMode src', () async {
|
||||
_renderCirclesScene(BlendMode.src);
|
||||
await matchGoldenFile('shadermask_linear_src.png', region: const Rect.fromLTWH(0, 0, 360, 200));
|
||||
}, skip: isSafari || isFirefox);
|
||||
|
||||
/// Should render text with gradient.
|
||||
test('Renders text with linear gradient shader mask', () async {
|
||||
_renderTextScene(BlendMode.srcIn);
|
||||
await matchGoldenFile(
|
||||
'shadermask_linear_text.png',
|
||||
region: const Rect.fromLTWH(0, 0, 360, 200),
|
||||
);
|
||||
}, skip: isSafari || isFirefox);
|
||||
}
|
||||
|
||||
Picture _drawTestPictureWithCircles(Rect region, double offsetX, double offsetY) {
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(region);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 30, offsetY + 30),
|
||||
30,
|
||||
SurfacePaint()..style = PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 110, offsetY + 30),
|
||||
30,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xFFFF0000),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 30, offsetY + 110),
|
||||
30,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xFF00FF00),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 110, offsetY + 110),
|
||||
30,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0xFF0000FF),
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
void _renderCirclesScene(BlendMode blendMode) {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 400, 400);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture circles1 = _drawTestPictureWithCircles(region, 10, 10);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
const Rect shaderBounds = Rect.fromLTWH(180, 10, 140, 140);
|
||||
|
||||
final EngineGradient shader = GradientLinear(
|
||||
Offset(200 - shaderBounds.left, 30 - shaderBounds.top),
|
||||
Offset(320 - shaderBounds.left, 150 - shaderBounds.top),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
|
||||
builder.pushShaderMask(shader, shaderBounds, blendMode);
|
||||
final Picture circles2 = _drawTestPictureWithCircles(region, 180, 10);
|
||||
builder.addPicture(Offset.zero, circles2);
|
||||
builder.pop();
|
||||
|
||||
sceneHost.append(builder.build().webOnlyRootElement!);
|
||||
}
|
||||
|
||||
Picture _drawTestPictureWithText(Rect region, double offsetX, double offsetY) {
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(region);
|
||||
const String text = 'Shader test';
|
||||
|
||||
final EngineParagraphStyle paragraphStyle = EngineParagraphStyle(
|
||||
fontFamily: 'Roboto',
|
||||
fontSize: 40.0,
|
||||
);
|
||||
|
||||
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(paragraphStyle);
|
||||
builder.pushStyle(EngineTextStyle.only(color: const Color(0xFFFF0000)));
|
||||
builder.addText(text);
|
||||
final CanvasParagraph paragraph = builder.build();
|
||||
|
||||
const double maxWidth = 200 - 10;
|
||||
paragraph.layout(const ParagraphConstraints(width: maxWidth));
|
||||
canvas.drawParagraph(paragraph, Offset(offsetX, offsetY));
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
void _renderTextScene(BlendMode blendMode) {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 600, 400);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture textPicture = _drawTestPictureWithText(region, 10, 10);
|
||||
builder.addPicture(Offset.zero, textPicture);
|
||||
|
||||
const List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
const Rect shaderBounds = Rect.fromLTWH(180, 10, 140, 140);
|
||||
|
||||
final EngineGradient shader = GradientLinear(
|
||||
Offset(200 - shaderBounds.left, 30 - shaderBounds.top),
|
||||
Offset(320 - shaderBounds.left, 150 - shaderBounds.top),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
|
||||
builder.pushShaderMask(shader, shaderBounds, blendMode);
|
||||
|
||||
final Picture textPicture2 = _drawTestPictureWithText(region, 180, 10);
|
||||
builder.addPicture(Offset.zero, textPicture2);
|
||||
builder.pop();
|
||||
|
||||
sceneHost.append(builder.build().webOnlyRootElement!);
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
|
||||
const Color _kShadowColor = Color.fromARGB(255, 0, 0, 0);
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
const Rect region = Rect.fromLTWH(0, 0, 550, 300);
|
||||
|
||||
late SurfaceSceneBuilder builder;
|
||||
|
||||
setUpUnitTests(emulateTesterEnvironment: false, setUpTestViewDimensions: false);
|
||||
|
||||
setUp(() {
|
||||
builder = SurfaceSceneBuilder();
|
||||
});
|
||||
|
||||
void paintShapeOutline() {
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(Rect.largest);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTRB(0.0, 0.0, 20.0, 20.0),
|
||||
SurfacePaint()
|
||||
..color = const Color.fromARGB(255, 0, 0, 255)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.0,
|
||||
);
|
||||
builder.addPicture(Offset.zero, recorder.endRecording());
|
||||
}
|
||||
|
||||
void paintShadowBounds(SurfacePath path, double elevation) {
|
||||
final Rect shadowBounds = computePenumbraBounds(path.getBounds(), elevation);
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(Rect.largest);
|
||||
canvas.drawRect(
|
||||
shadowBounds,
|
||||
SurfacePaint()
|
||||
..color = const Color.fromARGB(255, 0, 255, 0)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.0,
|
||||
);
|
||||
builder.addPicture(Offset.zero, recorder.endRecording());
|
||||
}
|
||||
|
||||
void paintBitmapCanvasShadow(double elevation, Offset offset, bool transparentOccluder) {
|
||||
final SurfacePath path = SurfacePath()..addRect(const Rect.fromLTRB(0, 0, 20, 20));
|
||||
builder.pushOffset(offset.dx, offset.dy);
|
||||
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(Rect.largest);
|
||||
canvas.debugEnforceArbitraryPaint(); // make sure DOM canvas doesn't take over
|
||||
canvas.drawShadow(path, _kShadowColor, elevation, transparentOccluder);
|
||||
builder.addPicture(Offset.zero, recorder.endRecording());
|
||||
paintShapeOutline();
|
||||
paintShadowBounds(path, elevation);
|
||||
|
||||
builder.pop(); // offset
|
||||
}
|
||||
|
||||
void paintBitmapCanvasComplexPathShadow(double elevation, Offset offset) {
|
||||
final SurfacePath path =
|
||||
SurfacePath()
|
||||
..moveTo(10, 0)
|
||||
..lineTo(20, 10)
|
||||
..lineTo(10, 20)
|
||||
..lineTo(0, 10)
|
||||
..close();
|
||||
builder.pushOffset(offset.dx, offset.dy);
|
||||
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(Rect.largest);
|
||||
canvas.debugEnforceArbitraryPaint(); // make sure DOM canvas doesn't take over
|
||||
canvas.drawShadow(path, _kShadowColor, elevation, false);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
SurfacePaint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const Color.fromARGB(255, 0, 0, 255),
|
||||
);
|
||||
builder.addPicture(Offset.zero, recorder.endRecording());
|
||||
paintShadowBounds(path, elevation);
|
||||
|
||||
builder.pop(); // offset
|
||||
}
|
||||
|
||||
test('renders shadows correctly', () async {
|
||||
// Physical shape clips. We want to see that clipping in the screenshot.
|
||||
debugShowClipLayers = false;
|
||||
|
||||
builder.pushOffset(10, 20);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
paintBitmapCanvasShadow(i.toDouble(), Offset(50.0 * i, 60), false);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
paintBitmapCanvasShadow(i.toDouble(), Offset(50.0 * i, 120), true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
paintBitmapCanvasComplexPathShadow(i.toDouble(), Offset(50.0 * i, 180));
|
||||
}
|
||||
|
||||
builder.pop();
|
||||
|
||||
final DomElement sceneElement = builder.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(sceneElement);
|
||||
|
||||
await matchGoldenFile('shadows.png', region: region);
|
||||
}, testOn: 'chrome');
|
||||
|
||||
/// For dart testing having `no tests ran` in a file is considered an error
|
||||
/// and result in exit code 1.
|
||||
/// See: https://github.com/dart-lang/test/pull/1173
|
||||
///
|
||||
/// Since screenshot tests run one by one and exit code is checked immediately
|
||||
/// after that a test file that only runs in chrome will break the other
|
||||
/// browsers. This method is added as a bandaid solution.
|
||||
test('dummy tests to pass on other browsers', () async {
|
||||
expect(2 + 2, 4);
|
||||
});
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
// 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 'dart:typed_data';
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
final Float32List points = Float32List(PathIterator.kMaxBufferSize);
|
||||
|
||||
group('PathIterator', () {
|
||||
test('Should return done verb for empty path', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
final PathIterator iter = PathIterator(path.pathRef, false);
|
||||
expect(iter.peek(), SPath.kDoneVerb);
|
||||
expect(iter.next(points), SPath.kDoneVerb);
|
||||
});
|
||||
|
||||
test('Should return done when moveTo is last instruction', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(10, 10);
|
||||
final PathIterator iter = PathIterator(path.pathRef, false);
|
||||
expect(iter.peek(), SPath.kMoveVerb);
|
||||
expect(iter.next(points), SPath.kDoneVerb);
|
||||
});
|
||||
|
||||
test('Should return lineTo', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(10, 10);
|
||||
path.lineTo(20, 20);
|
||||
final PathIterator iter = PathIterator(path.pathRef, false);
|
||||
expect(iter.peek(), SPath.kMoveVerb);
|
||||
expect(iter.next(points), SPath.kMoveVerb);
|
||||
expect(points[0], 10);
|
||||
expect(iter.next(points), SPath.kLineVerb);
|
||||
expect(points[2], 20);
|
||||
expect(iter.next(points), SPath.kDoneVerb);
|
||||
});
|
||||
|
||||
test('Should return extra lineTo if iteration is closed', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(10, 10);
|
||||
path.lineTo(20, 20);
|
||||
final PathIterator iter = PathIterator(path.pathRef, true);
|
||||
expect(iter.peek(), SPath.kMoveVerb);
|
||||
expect(iter.next(points), SPath.kMoveVerb);
|
||||
expect(points[0], 10);
|
||||
expect(iter.next(points), SPath.kLineVerb);
|
||||
expect(points[2], 20);
|
||||
expect(iter.next(points), SPath.kLineVerb);
|
||||
expect(points[2], 10);
|
||||
expect(iter.next(points), SPath.kCloseVerb);
|
||||
expect(iter.next(points), SPath.kDoneVerb);
|
||||
});
|
||||
|
||||
test('Should not return extra lineTo if last point is starting point', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(10, 10);
|
||||
path.lineTo(20, 20);
|
||||
path.lineTo(10, 10);
|
||||
final PathIterator iter = PathIterator(path.pathRef, true);
|
||||
expect(iter.peek(), SPath.kMoveVerb);
|
||||
expect(iter.next(points), SPath.kMoveVerb);
|
||||
expect(points[0], 10);
|
||||
expect(iter.next(points), SPath.kLineVerb);
|
||||
expect(points[2], 20);
|
||||
expect(iter.next(points), SPath.kLineVerb);
|
||||
expect(points[2], 10);
|
||||
expect(iter.next(points), SPath.kCloseVerb);
|
||||
expect(iter.next(points), SPath.kDoneVerb);
|
||||
});
|
||||
|
||||
test('peek should return lineTo if iteration is closed', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(10, 10);
|
||||
path.lineTo(20, 20);
|
||||
final PathIterator iter = PathIterator(path.pathRef, true);
|
||||
expect(iter.next(points), SPath.kMoveVerb);
|
||||
expect(iter.next(points), SPath.kLineVerb);
|
||||
expect(iter.peek(), SPath.kLineVerb);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,473 +0,0 @@
|
||||
// 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 'dart:math' as math;
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' hide window;
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
/// Test winding and convexity of a path.
|
||||
void testMain() {
|
||||
group('Convexity', () {
|
||||
test('Empty path should be convex', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
expect(path.isConvex, isTrue);
|
||||
});
|
||||
|
||||
test('Circle should be convex', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.addOval(const Rect.fromLTRB(0, 0, 20, 20));
|
||||
expect(path.isConvex, isTrue);
|
||||
// 2nd circle.
|
||||
path.addOval(const Rect.fromLTRB(0, 0, 20, 20));
|
||||
expect(path.isConvex, isFalse);
|
||||
});
|
||||
|
||||
test('addRect should be convex', () {
|
||||
SurfacePath path = SurfacePath();
|
||||
path.addRect(const Rect.fromLTRB(0, 0, 20, 20));
|
||||
expect(path.isConvex, isTrue);
|
||||
|
||||
path = SurfacePath();
|
||||
path.addRectWithDirection(const Rect.fromLTRB(0, 0, 20, 20), SPathDirection.kCW, 0);
|
||||
expect(path.isConvex, isTrue);
|
||||
|
||||
path = SurfacePath();
|
||||
path.addRectWithDirection(const Rect.fromLTRB(0, 0, 20, 20), SPathDirection.kCCW, 0);
|
||||
expect(path.isConvex, isTrue);
|
||||
});
|
||||
|
||||
test('Quad should be convex', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.quadraticBezierTo(100, 100, 50, 50);
|
||||
expect(path.isConvex, isTrue);
|
||||
});
|
||||
|
||||
test('moveto/lineto convexity', () {
|
||||
final List<LineTestCase> testCases = <LineTestCase>[
|
||||
LineTestCase('', SPathConvexityType.kConvex),
|
||||
LineTestCase('0 0', SPathConvexityType.kConvex),
|
||||
LineTestCase('0 0 10 10', SPathConvexityType.kConvex),
|
||||
LineTestCase('0 0 10 10 20 20 0 0 10 10', SPathConvexityType.kConcave),
|
||||
LineTestCase('0 0 10 10 10 20', SPathConvexityType.kConvex),
|
||||
LineTestCase('0 0 10 10 10 0', SPathConvexityType.kConvex),
|
||||
LineTestCase('0 0 10 10 10 0 0 10', SPathConvexityType.kConcave),
|
||||
LineTestCase('0 0 10 0 0 10 -10 -10', SPathConvexityType.kConcave),
|
||||
];
|
||||
|
||||
for (final LineTestCase testCase in testCases) {
|
||||
final SurfacePath path = SurfacePath();
|
||||
setFromString(path, testCase.pathContent);
|
||||
expect(path.convexityType, testCase.convexity);
|
||||
}
|
||||
});
|
||||
|
||||
test('Convexity of path with infinite points should return unknown', () {
|
||||
const List<Offset> nonFinitePts = <Offset>[
|
||||
Offset(double.infinity, 0),
|
||||
Offset(0, double.infinity),
|
||||
Offset.infinite,
|
||||
Offset(double.negativeInfinity, 0),
|
||||
Offset(0, double.negativeInfinity),
|
||||
Offset(double.negativeInfinity, double.negativeInfinity),
|
||||
Offset(double.negativeInfinity, double.infinity),
|
||||
Offset(double.infinity, double.negativeInfinity),
|
||||
Offset(double.nan, 0),
|
||||
Offset(0, double.nan),
|
||||
Offset(double.nan, double.nan),
|
||||
];
|
||||
final int nonFinitePointsCount = nonFinitePts.length;
|
||||
|
||||
const List<Offset> axisAlignedPts = <Offset>[
|
||||
Offset(kScalarMax, 0),
|
||||
Offset(0, kScalarMax),
|
||||
Offset(kScalarMin, 0),
|
||||
Offset(0, kScalarMin),
|
||||
];
|
||||
final int axisAlignedPointsCount = axisAlignedPts.length;
|
||||
|
||||
final SurfacePath path = SurfacePath();
|
||||
|
||||
for (int index = 0; index < (13 * nonFinitePointsCount * axisAlignedPointsCount); index++) {
|
||||
final int i = index % nonFinitePointsCount;
|
||||
final int f = index % axisAlignedPointsCount;
|
||||
final int g = (f + 1) % axisAlignedPointsCount;
|
||||
path.reset();
|
||||
switch (index % 13) {
|
||||
case 0:
|
||||
path.lineTo(nonFinitePts[i].dx, nonFinitePts[i].dy);
|
||||
case 1:
|
||||
path.quadraticBezierTo(
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
);
|
||||
case 2:
|
||||
path.quadraticBezierTo(
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
);
|
||||
case 3:
|
||||
path.quadraticBezierTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
);
|
||||
case 4:
|
||||
path.cubicTo(
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
);
|
||||
case 5:
|
||||
path.cubicTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
);
|
||||
case 6:
|
||||
path.cubicTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
);
|
||||
case 7:
|
||||
path.cubicTo(
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
);
|
||||
case 8:
|
||||
path.cubicTo(
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
);
|
||||
case 9:
|
||||
path.cubicTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
);
|
||||
case 10:
|
||||
path.cubicTo(
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
);
|
||||
case 11:
|
||||
path.cubicTo(
|
||||
nonFinitePts[i].dx,
|
||||
nonFinitePts[i].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
);
|
||||
case 12:
|
||||
path.moveTo(nonFinitePts[i].dx, nonFinitePts[i].dy);
|
||||
}
|
||||
expect(path.convexityType, SPathConvexityType.kUnknown);
|
||||
}
|
||||
|
||||
for (int index = 0; index < (11 * axisAlignedPointsCount); ++index) {
|
||||
final int f = index % axisAlignedPointsCount;
|
||||
final int g = (f + 1) % axisAlignedPointsCount;
|
||||
path.reset();
|
||||
final int curveSelect = index % 11;
|
||||
switch (curveSelect) {
|
||||
case 0:
|
||||
path.moveTo(axisAlignedPts[f].dx, axisAlignedPts[f].dy);
|
||||
case 1:
|
||||
path.lineTo(axisAlignedPts[f].dx, axisAlignedPts[f].dy);
|
||||
case 2:
|
||||
path.quadraticBezierTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
);
|
||||
case 3:
|
||||
path.quadraticBezierTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
);
|
||||
case 4:
|
||||
path.quadraticBezierTo(
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
);
|
||||
case 5:
|
||||
path.cubicTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
);
|
||||
case 6:
|
||||
path.cubicTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
);
|
||||
case 7:
|
||||
path.cubicTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
);
|
||||
case 8:
|
||||
path.cubicTo(
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
);
|
||||
case 9:
|
||||
path.cubicTo(
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
);
|
||||
case 10:
|
||||
path.cubicTo(
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
axisAlignedPts[f].dx,
|
||||
axisAlignedPts[f].dy,
|
||||
axisAlignedPts[g].dx,
|
||||
axisAlignedPts[g].dy,
|
||||
);
|
||||
}
|
||||
if (curveSelect != 7 && curveSelect != 10) {
|
||||
final int result = path.convexityType;
|
||||
expect(result, SPathConvexityType.kConvex);
|
||||
} else {
|
||||
// we make a copy so that we don't cache the result on the passed
|
||||
// in path.
|
||||
final SurfacePath path2 = SurfacePath.from(path);
|
||||
final int c = path2.convexityType;
|
||||
assert(SPathConvexityType.kUnknown == c || SPathConvexityType.kConcave == c);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('Concave lines path', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(-0.284071773, -0.0622361786);
|
||||
path.lineTo(-0.284072, -0.0622351);
|
||||
path.lineTo(-0.28407, -0.0622307);
|
||||
path.lineTo(-0.284067, -0.0622182);
|
||||
path.lineTo(-0.284084, -0.0622269);
|
||||
path.lineTo(-0.284072, -0.0622362);
|
||||
path.close();
|
||||
expect(path.convexityType, SPathConvexityType.kConcave);
|
||||
});
|
||||
|
||||
test('Single moveTo origin', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(0, 0);
|
||||
expect(path.convexityType, SPathConvexityType.kConvex);
|
||||
});
|
||||
|
||||
test('Single diagonal line', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(12, 20);
|
||||
path.lineTo(-12, -20);
|
||||
expect(path.convexityType, SPathConvexityType.kConvex);
|
||||
});
|
||||
|
||||
test('TriLeft', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(1, 0);
|
||||
path.lineTo(1, 1);
|
||||
path.close();
|
||||
expect(path.convexityType, SPathConvexityType.kConvex);
|
||||
});
|
||||
|
||||
test('TriRight', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(-1, 0);
|
||||
path.lineTo(1, 1);
|
||||
path.close();
|
||||
expect(path.convexityType, SPathConvexityType.kConvex);
|
||||
});
|
||||
|
||||
test('square', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(1, 0);
|
||||
path.lineTo(1, 1);
|
||||
path.lineTo(0, 1);
|
||||
path.close();
|
||||
expect(path.convexityType, SPathConvexityType.kConvex);
|
||||
});
|
||||
|
||||
test('redundant square', () {
|
||||
final SurfacePath redundantSquare = SurfacePath();
|
||||
redundantSquare.moveTo(0, 0);
|
||||
redundantSquare.lineTo(0, 0);
|
||||
redundantSquare.lineTo(0, 0);
|
||||
redundantSquare.lineTo(1, 0);
|
||||
redundantSquare.lineTo(1, 0);
|
||||
redundantSquare.lineTo(1, 0);
|
||||
redundantSquare.lineTo(1, 1);
|
||||
redundantSquare.lineTo(1, 1);
|
||||
redundantSquare.lineTo(1, 1);
|
||||
redundantSquare.lineTo(0, 1);
|
||||
redundantSquare.lineTo(0, 1);
|
||||
redundantSquare.lineTo(0, 1);
|
||||
redundantSquare.close();
|
||||
expect(redundantSquare.convexityType, SPathConvexityType.kConvex);
|
||||
});
|
||||
|
||||
test('bowtie', () {
|
||||
final SurfacePath bowTie = SurfacePath();
|
||||
bowTie.moveTo(0, 0);
|
||||
bowTie.lineTo(0, 0);
|
||||
bowTie.lineTo(0, 0);
|
||||
bowTie.lineTo(1, 1);
|
||||
bowTie.lineTo(1, 1);
|
||||
bowTie.lineTo(1, 1);
|
||||
bowTie.lineTo(1, 0);
|
||||
bowTie.lineTo(1, 0);
|
||||
bowTie.lineTo(1, 0);
|
||||
bowTie.lineTo(0, 1);
|
||||
bowTie.lineTo(0, 1);
|
||||
bowTie.lineTo(0, 1);
|
||||
bowTie.close();
|
||||
expect(bowTie.convexityType, SPathConvexityType.kConcave);
|
||||
});
|
||||
|
||||
test('sprial', () {
|
||||
final SurfacePath spiral = SurfacePath();
|
||||
spiral.moveTo(0, 0);
|
||||
spiral.lineTo(100, 0);
|
||||
spiral.lineTo(100, 100);
|
||||
spiral.lineTo(0, 100);
|
||||
spiral.lineTo(0, 50);
|
||||
spiral.lineTo(50, 50);
|
||||
spiral.lineTo(50, 75);
|
||||
spiral.close();
|
||||
expect(spiral.convexityType, SPathConvexityType.kConcave);
|
||||
});
|
||||
|
||||
test('dent', () {
|
||||
final SurfacePath dent = SurfacePath();
|
||||
dent.moveTo(0, 0);
|
||||
dent.lineTo(100, 100);
|
||||
dent.lineTo(0, 100);
|
||||
dent.lineTo(-50, 200);
|
||||
dent.lineTo(-200, 100);
|
||||
dent.close();
|
||||
expect(dent.convexityType, SPathConvexityType.kConcave);
|
||||
});
|
||||
|
||||
test('degenerate segments1', () {
|
||||
final SurfacePath strokedSin = SurfacePath();
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
final double x = i.toDouble() / 2.0;
|
||||
final double y = 500 - (x + math.sin(x / 100) * 40) / 3;
|
||||
if (0 == i) {
|
||||
strokedSin.moveTo(x, y);
|
||||
} else {
|
||||
strokedSin.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
expect(strokedSin.convexityType, SPathConvexityType.kConcave);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/66560.
|
||||
test('Quadratic', () {
|
||||
final SurfacePath path = SurfacePath();
|
||||
path.moveTo(100.0, 0.0);
|
||||
path.quadraticBezierTo(200.0, 0.0, 200.0, 100.0);
|
||||
path.quadraticBezierTo(200.0, 200.0, 100.0, 200.0);
|
||||
path.quadraticBezierTo(0.0, 200.0, 0.0, 100.0);
|
||||
path.quadraticBezierTo(0.0, 0.0, 100.0, 0.0);
|
||||
path.close();
|
||||
expect(path.contains(const Offset(100, 20)), isTrue);
|
||||
expect(path.contains(const Offset(100, 120)), isTrue);
|
||||
expect(path.contains(const Offset(100, -10)), isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class LineTestCase {
|
||||
LineTestCase(this.pathContent, this.convexity);
|
||||
final String pathContent;
|
||||
final int convexity;
|
||||
}
|
||||
|
||||
/// Parses a string of the format "mx my lx1 ly1 lx2 ly2..." into a path
|
||||
/// with moveTo/lineTo instructions for points.
|
||||
void setFromString(SurfacePath path, String value) {
|
||||
bool first = true;
|
||||
final List<String> points = value.split(' ');
|
||||
if (points.length < 2) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < points.length; i += 2) {
|
||||
if (first) {
|
||||
path.moveTo(double.parse(points[i]), double.parse(points[i + 1]));
|
||||
first = false;
|
||||
} else {
|
||||
path.lineTo(double.parse(points[i]), double.parse(points[i + 1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scalar max is based on 32 bit float since [PathRef] stores values in
|
||||
// Float32List.
|
||||
const double kScalarMax = 3.402823466e+38;
|
||||
const double kScalarMin = -kScalarMax;
|
||||
@@ -1,93 +0,0 @@
|
||||
// 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 'dart:async';
|
||||
|
||||
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:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
|
||||
import '../../common/matchers.dart';
|
||||
import '../../common/test_initialization.dart';
|
||||
|
||||
const MethodCodec codec = StandardMethodCodec();
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
await bootstrapAndRunApp(withImplicitView: true);
|
||||
|
||||
late PersistedPlatformView view;
|
||||
|
||||
group('PersistedPlatformView', () {
|
||||
setUp(() async {
|
||||
ui_web.platformViewRegistry.registerViewFactory(
|
||||
'test-0',
|
||||
(int viewId) => createDomHTMLDivElement(),
|
||||
);
|
||||
ui_web.platformViewRegistry.registerViewFactory(
|
||||
'test-1',
|
||||
(int viewId) => createDomHTMLDivElement(),
|
||||
);
|
||||
// Ensure the views are created...
|
||||
await Future.wait(<Future<void>>[
|
||||
_createPlatformView(0, 'test-0'),
|
||||
_createPlatformView(1, 'test-1'),
|
||||
]);
|
||||
view = PersistedPlatformView(0, 0, 0, 100, 100)..build();
|
||||
});
|
||||
|
||||
group('update', () {
|
||||
test('throws assertion error if called with different viewIds', () {
|
||||
final PersistedPlatformView differentView = PersistedPlatformView(1, 1, 1, 100, 100)
|
||||
..build();
|
||||
expect(() {
|
||||
view.update(differentView);
|
||||
}, throwsAssertionError);
|
||||
});
|
||||
});
|
||||
|
||||
group('canUpdateAsMatch', () {
|
||||
test('returns true when viewId is the same', () {
|
||||
final PersistedPlatformView sameView = PersistedPlatformView(0, 1, 1, 100, 100)..build();
|
||||
expect(view.canUpdateAsMatch(sameView), isTrue);
|
||||
});
|
||||
|
||||
test('returns false when viewId is different', () {
|
||||
final PersistedPlatformView differentView = PersistedPlatformView(1, 1, 1, 100, 100)
|
||||
..build();
|
||||
expect(view.canUpdateAsMatch(differentView), isFalse);
|
||||
});
|
||||
|
||||
test('returns false when other view is not a PlatformView', () {
|
||||
final PersistedOpacity anyView = PersistedOpacity(null, 1, ui.Offset.zero)..build();
|
||||
expect(view.canUpdateAsMatch(anyView), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('createElement', () {
|
||||
test('creates slot element that can receive pointer events', () {
|
||||
final DomElement element = view.createElement();
|
||||
|
||||
expect(element.tagName, equalsIgnoringCase('flt-platform-view-slot'));
|
||||
expect(element.style.pointerEvents, 'auto');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Sends a platform message to create a Platform View with the given id and viewType.
|
||||
Future<void> _createPlatformView(int id, String viewType) {
|
||||
final Completer<void> completer = Completer<void>();
|
||||
ui.PlatformDispatcher.instance.sendPlatformMessage(
|
||||
'flutter/platform_views',
|
||||
codec.encodeMethodCall(MethodCall('create', <String, dynamic>{'id': id, 'viewType': viewType})),
|
||||
(dynamic _) => completer.complete(),
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
@@ -1,961 +0,0 @@
|
||||
// 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.
|
||||
|
||||
@TestOn('chrome || firefox')
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:js_interop';
|
||||
import 'dart:js_util' as js_util;
|
||||
|
||||
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/matchers.dart';
|
||||
import '../../common/rendering.dart';
|
||||
import '../../common/test_initialization.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
setUpAll(() async {
|
||||
await bootstrapAndRunApp(withImplicitView: true);
|
||||
setUpRenderingForTests();
|
||||
});
|
||||
|
||||
group('SceneBuilder', () {
|
||||
test('pushOffset implements surface lifecycle', () {
|
||||
testLayerLifeCycle(
|
||||
(ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
|
||||
return sceneBuilder.pushOffset(10, 20, oldLayer: oldLayer as ui.OffsetEngineLayer?);
|
||||
},
|
||||
() {
|
||||
return '''<flt-scene><flt-offset></flt-offset></flt-scene>''';
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('pushTransform implements surface lifecycle', () {
|
||||
testLayerLifeCycle(
|
||||
(ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
|
||||
return sceneBuilder.pushTransform(
|
||||
(Matrix4.identity()..scale(EngineFlutterDisplay.instance.browserDevicePixelRatio))
|
||||
.toFloat64(),
|
||||
);
|
||||
},
|
||||
() {
|
||||
return '''<flt-scene><flt-transform></flt-transform></flt-scene>''';
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('pushClipRect implements surface lifecycle', () {
|
||||
testLayerLifeCycle(
|
||||
(ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
|
||||
return sceneBuilder.pushClipRect(
|
||||
const ui.Rect.fromLTRB(10, 20, 30, 40),
|
||||
oldLayer: oldLayer as ui.ClipRectEngineLayer?,
|
||||
);
|
||||
},
|
||||
() {
|
||||
return '''
|
||||
<flt-scene>
|
||||
<flt-clip><flt-clip-interior></flt-clip-interior></flt-clip>
|
||||
</flt-scene>
|
||||
''';
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('pushClipRRect implements surface lifecycle', () {
|
||||
testLayerLifeCycle(
|
||||
(ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
|
||||
return sceneBuilder.pushClipRRect(
|
||||
ui.RRect.fromLTRBR(10, 20, 30, 40, const ui.Radius.circular(3)),
|
||||
oldLayer: oldLayer as ui.ClipRRectEngineLayer?,
|
||||
clipBehavior: ui.Clip.none,
|
||||
);
|
||||
},
|
||||
() {
|
||||
return '''
|
||||
<flt-scene>
|
||||
<flt-clip clip-type="rrect">
|
||||
<flt-clip-interior></flt-clip-interior>
|
||||
</flt-clip>
|
||||
</flt-scene>
|
||||
''';
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('pushClipPath implements surface lifecycle', () {
|
||||
testLayerLifeCycle(
|
||||
(ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
|
||||
final ui.Path path = ui.Path()..addRect(const ui.Rect.fromLTRB(10, 20, 30, 40));
|
||||
return sceneBuilder.pushClipPath(path, oldLayer: oldLayer as ui.ClipPathEngineLayer?);
|
||||
},
|
||||
() {
|
||||
return '''
|
||||
<flt-scene>
|
||||
<flt-clippath>
|
||||
<svg><defs><clipPath><path></path></clipPath></defs></svg>
|
||||
</flt-clippath>
|
||||
</flt-scene>
|
||||
''';
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('pushOpacity implements surface lifecycle', () {
|
||||
testLayerLifeCycle(
|
||||
(ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
|
||||
return sceneBuilder.pushOpacity(10, oldLayer: oldLayer as ui.OpacityEngineLayer?);
|
||||
},
|
||||
() {
|
||||
return '''<flt-scene><flt-opacity></flt-opacity></flt-scene>''';
|
||||
},
|
||||
);
|
||||
});
|
||||
test('pushBackdropFilter implements surface lifecycle', () {
|
||||
testLayerLifeCycle(
|
||||
(ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
|
||||
return sceneBuilder.pushBackdropFilter(
|
||||
ui.ImageFilter.blur(sigmaX: 1.0, sigmaY: 1.0),
|
||||
oldLayer: oldLayer as ui.BackdropFilterEngineLayer?,
|
||||
);
|
||||
},
|
||||
() {
|
||||
return '''
|
||||
<flt-scene>
|
||||
<flt-backdrop>
|
||||
<flt-backdrop-filter></flt-backdrop-filter>
|
||||
<flt-backdrop-interior></flt-backdrop-interior>
|
||||
</flt-backdrop>
|
||||
</flt-scene>''';
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('parent child lifecycle', () {
|
||||
test('build, retain, update, and applyPaint are called the right number of times', () {
|
||||
final PersistedScene scene1 = PersistedScene(null);
|
||||
final PersistedClipRect clip1 = PersistedClipRect(
|
||||
null,
|
||||
const ui.Rect.fromLTRB(10, 10, 20, 20),
|
||||
ui.Clip.antiAlias,
|
||||
);
|
||||
final PersistedOpacity opacity = PersistedOpacity(null, 100, ui.Offset.zero);
|
||||
final MockPersistedPicture picture = MockPersistedPicture();
|
||||
|
||||
scene1.appendChild(clip1);
|
||||
clip1.appendChild(opacity);
|
||||
opacity.appendChild(picture);
|
||||
|
||||
expect(picture.retainCount, 0);
|
||||
expect(picture.buildCount, 0);
|
||||
expect(picture.updateCount, 0);
|
||||
expect(picture.applyPaintCount, 0);
|
||||
|
||||
scene1.preroll(PrerollSurfaceContext());
|
||||
scene1.build();
|
||||
commitScene(scene1);
|
||||
expect(picture.retainCount, 0);
|
||||
expect(picture.buildCount, 1);
|
||||
expect(picture.updateCount, 0);
|
||||
expect(picture.applyPaintCount, 1);
|
||||
|
||||
// The second scene graph retains the opacity, but not the clip. However,
|
||||
// because the clip didn't change no repaints should happen.
|
||||
final PersistedScene scene2 = PersistedScene(scene1);
|
||||
final PersistedClipRect clip2 = PersistedClipRect(
|
||||
clip1,
|
||||
const ui.Rect.fromLTRB(10, 10, 20, 20),
|
||||
ui.Clip.antiAlias,
|
||||
);
|
||||
clip1.state = PersistedSurfaceState.pendingUpdate;
|
||||
scene2.appendChild(clip2);
|
||||
opacity.state = PersistedSurfaceState.pendingRetention;
|
||||
clip2.appendChild(opacity);
|
||||
|
||||
scene2.preroll(PrerollSurfaceContext());
|
||||
scene2.update(scene1);
|
||||
commitScene(scene2);
|
||||
expect(picture.retainCount, 1);
|
||||
expect(picture.buildCount, 1);
|
||||
expect(picture.updateCount, 0);
|
||||
expect(picture.applyPaintCount, 1);
|
||||
|
||||
// The third scene graph retains the opacity, and produces a new clip.
|
||||
// This should cause the picture to repaint despite being retained.
|
||||
final PersistedScene scene3 = PersistedScene(scene2);
|
||||
final PersistedClipRect clip3 = PersistedClipRect(
|
||||
clip2,
|
||||
const ui.Rect.fromLTRB(10, 10, 50, 50),
|
||||
ui.Clip.antiAlias,
|
||||
);
|
||||
clip2.state = PersistedSurfaceState.pendingUpdate;
|
||||
scene3.appendChild(clip3);
|
||||
opacity.state = PersistedSurfaceState.pendingRetention;
|
||||
clip3.appendChild(opacity);
|
||||
|
||||
scene3.preroll(PrerollSurfaceContext());
|
||||
scene3.update(scene2);
|
||||
commitScene(scene3);
|
||||
expect(picture.retainCount, 2);
|
||||
expect(picture.buildCount, 1);
|
||||
expect(picture.updateCount, 0);
|
||||
expect(picture.applyPaintCount, 2);
|
||||
});
|
||||
});
|
||||
|
||||
group('Compositing order', () {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/55058
|
||||
//
|
||||
// When BitmapCanvas uses multiple elements to paint, the very first
|
||||
// canvas needs to have a -1 zIndex so it can preserve compositing order.
|
||||
test('Canvas element should retain -1 zIndex after update', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final ui.Picture picture1 = _drawPicture();
|
||||
final ui.ClipRectEngineLayer oldLayer = builder.pushClipRect(
|
||||
const ui.Rect.fromLTRB(10, 10, 300, 300),
|
||||
);
|
||||
builder.addPicture(ui.Offset.zero, picture1);
|
||||
builder.pop();
|
||||
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
expect(content.querySelector('canvas')!.style.zIndex, '-1');
|
||||
|
||||
// Force update to scene which will utilize reuse code path.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.pushClipRect(const ui.Rect.fromLTRB(5, 10, 300, 300), oldLayer: oldLayer);
|
||||
final ui.Picture picture2 = _drawPicture();
|
||||
builder2.addPicture(ui.Offset.zero, picture2);
|
||||
builder2.pop();
|
||||
|
||||
final DomElement contentAfterReuse = builder2.build().webOnlyRootElement!;
|
||||
expect(contentAfterReuse.querySelector('canvas')!.style.zIndex, '-1');
|
||||
});
|
||||
|
||||
test('Multiple canvas elements should retain zIndex after update', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final ui.Picture picture1 = _drawPathImagePath();
|
||||
final ui.ClipRectEngineLayer oldLayer = builder.pushClipRect(
|
||||
const ui.Rect.fromLTRB(10, 10, 300, 300),
|
||||
);
|
||||
builder.addPicture(ui.Offset.zero, picture1);
|
||||
builder.pop();
|
||||
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(content);
|
||||
expect(content.querySelector('canvas')!.style.zIndex, '-1');
|
||||
|
||||
// Force update to scene which will utilize reuse code path.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.pushClipRect(const ui.Rect.fromLTRB(5, 10, 300, 300), oldLayer: oldLayer);
|
||||
final ui.Picture picture2 = _drawPathImagePath();
|
||||
builder2.addPicture(ui.Offset.zero, picture2);
|
||||
builder2.pop();
|
||||
|
||||
final DomElement contentAfterReuse = builder2.build().webOnlyRootElement!;
|
||||
final List<DomCanvasElement> list =
|
||||
contentAfterReuse.querySelectorAll('canvas').cast<DomCanvasElement>().toList();
|
||||
expect(list[0].style.zIndex, '-1');
|
||||
expect(list[1].style.zIndex, '');
|
||||
});
|
||||
});
|
||||
|
||||
/// Verify elementCache is passed during update to reuse existing
|
||||
/// image elements.
|
||||
test('Should retain same image element', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final ui.Picture picture1 = _drawPathImagePath();
|
||||
final ui.ClipRectEngineLayer oldLayer = builder.pushClipRect(
|
||||
const ui.Rect.fromLTRB(10, 10, 300, 300),
|
||||
);
|
||||
builder.addPicture(ui.Offset.zero, picture1);
|
||||
builder.pop();
|
||||
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(content);
|
||||
List<DomHTMLImageElement> list =
|
||||
content.querySelectorAll('img').cast<DomHTMLImageElement>().toList();
|
||||
for (final DomHTMLImageElement image in list) {
|
||||
image.alt = 'marked';
|
||||
}
|
||||
|
||||
// Force update to scene which will utilize reuse code path.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.pushClipRect(const ui.Rect.fromLTRB(5, 10, 300, 300), oldLayer: oldLayer);
|
||||
final ui.Picture picture2 = _drawPathImagePath();
|
||||
builder2.addPicture(ui.Offset.zero, picture2);
|
||||
builder2.pop();
|
||||
|
||||
final DomElement contentAfterReuse = builder2.build().webOnlyRootElement!;
|
||||
list = contentAfterReuse.querySelectorAll('img').cast<DomHTMLImageElement>().toList();
|
||||
for (final DomHTMLImageElement image in list) {
|
||||
expect(image.alt, 'marked');
|
||||
}
|
||||
expect(list.length, 1);
|
||||
});
|
||||
|
||||
PersistedPicture? findPictureSurfaceChild(PersistedContainerSurface parent) {
|
||||
PersistedPicture? pictureSurface;
|
||||
parent.visitChildren((PersistedSurface child) {
|
||||
pictureSurface = child as PersistedPicture;
|
||||
});
|
||||
return pictureSurface;
|
||||
}
|
||||
|
||||
test('skips painting picture when picture fully clipped out', () async {
|
||||
final ui.Picture picture = _drawPicture();
|
||||
|
||||
// Picture not clipped out, so we should see a `<flt-canvas>`
|
||||
{
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushOffset(0, 0);
|
||||
builder.addPicture(ui.Offset.zero, picture);
|
||||
builder.pop();
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
expect(content.querySelectorAll('flt-picture').single.children, isNotEmpty);
|
||||
}
|
||||
|
||||
// Picture fully clipped out, so we should not see a `<flt-canvas>`
|
||||
{
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushOffset(0, 0);
|
||||
final PersistedContainerSurface clip =
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(1000, 1000, 2000, 2000))
|
||||
as PersistedContainerSurface;
|
||||
builder.addPicture(ui.Offset.zero, picture);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
expect(content.querySelectorAll('flt-picture').single.children, isEmpty);
|
||||
expect(findPictureSurfaceChild(clip)!.canvas, isNull);
|
||||
}
|
||||
});
|
||||
|
||||
test('does not skip painting picture when picture is '
|
||||
'inside transform with offset', () async {
|
||||
final ui.Picture picture = _drawPicture();
|
||||
// Picture should not be clipped out since transform will offset it to 500,500
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushOffset(0, 0);
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(0, 0, 1000, 1000)) as PersistedContainerSurface;
|
||||
builder.pushTransform((Matrix4.identity()..scale(0.5, 0.5)).toFloat64());
|
||||
builder.addPicture(const ui.Offset(1000, 1000), picture);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
expect(content.querySelectorAll('flt-picture').single.children, isNotEmpty);
|
||||
});
|
||||
|
||||
test('does not skip painting picture when picture is '
|
||||
'inside transform', () async {
|
||||
final ui.Picture picture = _drawPicture();
|
||||
// Picture should not be clipped out since transform will offset it to 500,500
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushOffset(0, 0);
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(0, 0, 1000, 1000)) as PersistedContainerSurface;
|
||||
builder.pushTransform((Matrix4.identity()..scale(0.5, 0.5)).toFloat64());
|
||||
builder.pushOffset(1000, 1000);
|
||||
builder.addPicture(ui.Offset.zero, picture);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
expect(content.querySelectorAll('flt-picture').single.children, isNotEmpty);
|
||||
});
|
||||
|
||||
test('skips painting picture when picture fully clipped out with'
|
||||
' transform and offset', () async {
|
||||
final ui.Picture picture = _drawPicture();
|
||||
// Picture should be clipped out since transform will offset it to 500,500
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushOffset(50, 50);
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(0, 0, 1000, 1000)) as PersistedContainerSurface;
|
||||
builder.pushTransform((Matrix4.identity()..scale(2, 2)).toFloat64());
|
||||
builder.pushOffset(500, 500);
|
||||
builder.addPicture(ui.Offset.zero, picture);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
expect(content.querySelectorAll('flt-picture').single.children, isEmpty);
|
||||
});
|
||||
|
||||
test('releases old canvas when picture is fully clipped out after addRetained', () async {
|
||||
final ui.Picture picture = _drawPicture();
|
||||
|
||||
// Frame 1: picture visible
|
||||
final SurfaceSceneBuilder builder1 = SurfaceSceneBuilder();
|
||||
final PersistedOffset offset1 = builder1.pushOffset(0, 0) as PersistedOffset;
|
||||
builder1.addPicture(ui.Offset.zero, picture);
|
||||
builder1.pop();
|
||||
final DomElement content1 = builder1.build().webOnlyRootElement!;
|
||||
expect(content1.querySelectorAll('flt-picture').single.children, isNotEmpty);
|
||||
expect(findPictureSurfaceChild(offset1)!.canvas, isNotNull);
|
||||
|
||||
// Frame 2: picture is clipped out after an update
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
final PersistedOffset offset2 =
|
||||
builder2.pushOffset(-10000, -10000, oldLayer: offset1) as PersistedOffset;
|
||||
builder2.addPicture(ui.Offset.zero, picture);
|
||||
builder2.pop();
|
||||
final DomElement content = builder2.build().webOnlyRootElement!;
|
||||
expect(content.querySelectorAll('flt-picture').single.children, isEmpty);
|
||||
expect(findPictureSurfaceChild(offset2)!.canvas, isNull);
|
||||
});
|
||||
|
||||
test('releases old canvas when picture is fully clipped out after addRetained', () async {
|
||||
final ui.Picture picture = _drawPicture();
|
||||
|
||||
// Frame 1: picture visible
|
||||
final SurfaceSceneBuilder builder1 = SurfaceSceneBuilder();
|
||||
final PersistedOffset offset1 = builder1.pushOffset(0, 0) as PersistedOffset;
|
||||
final PersistedOffset subOffset1 = builder1.pushOffset(0, 0) as PersistedOffset;
|
||||
builder1.addPicture(ui.Offset.zero, picture);
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
final DomElement content1 = builder1.build().webOnlyRootElement!;
|
||||
expect(content1.querySelectorAll('flt-picture').single.children, isNotEmpty);
|
||||
expect(findPictureSurfaceChild(subOffset1)!.canvas, isNotNull);
|
||||
|
||||
// Frame 2: picture is clipped out after addRetained
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.pushOffset(-10000, -10000, oldLayer: offset1);
|
||||
|
||||
// Even though the child offset is added as retained, the parent
|
||||
// is updated with a value that causes the picture to move out of
|
||||
// the clipped area. We should see the canvas being released.
|
||||
builder2.addRetained(subOffset1);
|
||||
builder2.pop();
|
||||
final DomElement content = builder2.build().webOnlyRootElement!;
|
||||
expect(content.querySelectorAll('flt-picture').single.children, isEmpty);
|
||||
expect(findPictureSurfaceChild(subOffset1)!.canvas, isNull);
|
||||
});
|
||||
|
||||
test('auto-pops pushed layers', () async {
|
||||
final ui.Picture picture = _drawPicture();
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushOffset(0, 0);
|
||||
builder.pushOffset(0, 0);
|
||||
builder.pushOffset(0, 0);
|
||||
builder.pushOffset(0, 0);
|
||||
builder.pushOffset(0, 0);
|
||||
builder.addPicture(ui.Offset.zero, picture);
|
||||
|
||||
// Intentionally pop fewer layers than we pushed
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
// Expect as many layers as we pushed (not popped).
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
expect(content.querySelectorAll('flt-offset'), hasLength(5));
|
||||
});
|
||||
|
||||
test('updates child lists efficiently', () async {
|
||||
// Pushes a single child that renders one character.
|
||||
//
|
||||
// If the character is a number, pushes an offset layer. Otherwise, pushes
|
||||
// an offset layer. Test cases use this to control how layers are reused.
|
||||
// Layers of the same type can be reused even if they are not explicitly
|
||||
// updated. Conversely, layers of different types are never reused.
|
||||
ui.EngineLayer pushChild(SurfaceSceneBuilder builder, String char, {ui.EngineLayer? oldLayer}) {
|
||||
// Numbers use opacity layers, letters use offset layers. This is used to
|
||||
// control DOM reuse. Layers of the same type can reuse DOM nodes from other
|
||||
// dropped layers.
|
||||
final bool useOffset = int.tryParse(char) == null;
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(
|
||||
const ui.Rect.fromLTRB(0, 0, 400, 400),
|
||||
);
|
||||
final ui.Paragraph paragraph =
|
||||
(ui.ParagraphBuilder(ui.ParagraphStyle())
|
||||
..pushStyle(ui.TextStyle(decoration: ui.TextDecoration.lineThrough))
|
||||
..addText(char))
|
||||
.build();
|
||||
paragraph.layout(const ui.ParagraphConstraints(width: 1000));
|
||||
canvas.drawParagraph(paragraph, ui.Offset.zero);
|
||||
final ui.EngineLayer newLayer =
|
||||
useOffset
|
||||
? builder.pushOffset(
|
||||
0,
|
||||
0,
|
||||
oldLayer: oldLayer == null ? null : oldLayer as ui.OffsetEngineLayer,
|
||||
)
|
||||
: builder.pushOpacity(
|
||||
100,
|
||||
oldLayer: oldLayer == null ? null : oldLayer as ui.OpacityEngineLayer,
|
||||
);
|
||||
builder.addPicture(ui.Offset.zero, recorder.endRecording());
|
||||
builder.pop();
|
||||
return newLayer;
|
||||
}
|
||||
|
||||
// Maps letters to layers used to render them in the last frame, used to
|
||||
// supply `oldLayer` to guarantee update.
|
||||
final Map<String, ui.EngineLayer> renderedLayers = <String, ui.EngineLayer>{};
|
||||
|
||||
// Pump an empty scene to reset it, otherwise the first frame will attempt
|
||||
// to diff left-overs from a previous test, which results in unpredictable
|
||||
// DOM mutations.
|
||||
await renderScene(SurfaceSceneBuilder().build());
|
||||
|
||||
// Renders a `string` by breaking it up into individual characters and
|
||||
// rendering each character into its own layer.
|
||||
Future<void> testCase(
|
||||
String string,
|
||||
String description, {
|
||||
int deletions = 0,
|
||||
int additions = 0,
|
||||
int moves = 0,
|
||||
}) {
|
||||
final Set<DomNode> actualDeletions = <DomNode>{};
|
||||
final Set<DomNode> actualAdditions = <DomNode>{};
|
||||
|
||||
// Watches DOM mutations and counts deletions and additions to the child
|
||||
// list of the `<flt-scene>` element.
|
||||
final DomMutationObserver observer = createDomMutationObserver((
|
||||
JSArray<JSAny?> mutations,
|
||||
_,
|
||||
) {
|
||||
for (final DomMutationRecord record in mutations.toDart.cast<DomMutationRecord>()) {
|
||||
actualDeletions.addAll(record.removedNodes!);
|
||||
actualAdditions.addAll(record.addedNodes!);
|
||||
}
|
||||
});
|
||||
observer.observe(SurfaceSceneBuilder.debugLastFrameScene!.rootElement!, childList: true);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
for (int i = 0; i < string.length; i++) {
|
||||
final String char = string[i];
|
||||
renderedLayers[char] = pushChild(builder, char, oldLayer: renderedLayers[char]);
|
||||
}
|
||||
final SurfaceScene scene = builder.build();
|
||||
final List<DomElement> pTags =
|
||||
scene.webOnlyRootElement!.querySelectorAll('flt-paragraph').toList();
|
||||
expect(pTags, hasLength(string.length));
|
||||
expect(
|
||||
scene.webOnlyRootElement!
|
||||
.querySelectorAll('flt-paragraph')
|
||||
.map((DomElement p) => p.innerText)
|
||||
.join(),
|
||||
string,
|
||||
);
|
||||
renderedLayers.removeWhere((String key, ui.EngineLayer value) => !string.contains(key));
|
||||
|
||||
// Inject a zero-duration timer to allow mutation observers to receive notification.
|
||||
return Future<void>.delayed(Duration.zero).then((_) {
|
||||
observer.disconnect();
|
||||
|
||||
// Nodes that are removed then added are classified as "moves".
|
||||
final int actualMoves = actualAdditions.intersection(actualDeletions).length;
|
||||
// Compare all at once instead of one by one because when it fails, it's
|
||||
// much more useful to see all numbers, not just the one that failed to
|
||||
// match.
|
||||
expect(
|
||||
<String, int>{
|
||||
'additions': actualAdditions.length - actualMoves,
|
||||
'deletions': actualDeletions.length - actualMoves,
|
||||
'moves': actualMoves,
|
||||
},
|
||||
<String, int>{'additions': additions, 'deletions': deletions, 'moves': moves},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Adding
|
||||
await testCase('', 'noop');
|
||||
await testCase('', 'noop');
|
||||
await testCase('be', 'zero-to-many', additions: 2);
|
||||
await testCase('bcde', 'insert in the middle', additions: 2);
|
||||
await testCase('abcde', 'prepend', additions: 1);
|
||||
await testCase('abcdef', 'append', additions: 1);
|
||||
|
||||
// Moving
|
||||
await testCase('fbcdea', 'swap at ends', moves: 2);
|
||||
await testCase('fecdba', 'swap in the middle', moves: 2);
|
||||
await testCase('fedcba', 'swap adjacent in one move', moves: 1);
|
||||
await testCase('fedcba', 'non-empty noop');
|
||||
await testCase('afedcb', 'shift right by 1', moves: 1);
|
||||
await testCase('fedcba', 'shift left by 1', moves: 1);
|
||||
await testCase('abcdef', 'reverse', moves: 5);
|
||||
await testCase('efabcd', 'shift right by 2', moves: 2);
|
||||
await testCase('abcdef', 'shift left by 2', moves: 2);
|
||||
|
||||
// Scrolling without DOM reuse (numbers and letters use different types of layers)
|
||||
await testCase('9abcde', 'scroll right by 1', additions: 1, deletions: 1);
|
||||
await testCase('789abc', 'scroll right by 2', additions: 2, deletions: 2);
|
||||
await testCase('89abcd', 'scroll left by 1', additions: 1, deletions: 1);
|
||||
await testCase('abcdef', 'scroll left by 2', additions: 2, deletions: 2);
|
||||
|
||||
// Scrolling with DOM reuse
|
||||
await testCase('zabcde', 'scroll right by 1', moves: 1);
|
||||
await testCase('xyzabc', 'scroll right by 2', moves: 2);
|
||||
await testCase('yzabcd', 'scroll left by 1', moves: 1);
|
||||
await testCase('abcdef', 'scroll left by 2', moves: 2);
|
||||
|
||||
// Removing
|
||||
await testCase('bcdef', 'remove as start', deletions: 1);
|
||||
await testCase('bcde', 'remove as end', deletions: 1);
|
||||
await testCase('be', 'remove in the middle', deletions: 2);
|
||||
await testCase('', 'remove all', deletions: 2);
|
||||
});
|
||||
|
||||
test('Canvas should allocate fewer pixels when zoomed out', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final ui.Picture picture1 = _drawPicture();
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(10, 10, 300, 300));
|
||||
builder.addPicture(ui.Offset.zero, picture1);
|
||||
builder.pop();
|
||||
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
final DomCanvasElement canvas = content.querySelector('canvas')! as DomCanvasElement;
|
||||
final int unscaledWidth = canvas.width!.toInt();
|
||||
final int unscaledHeight = canvas.height!.toInt();
|
||||
|
||||
// Force update to scene which will utilize reuse code path.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.pushOffset(0, 0);
|
||||
builder2.pushTransform(Matrix4.identity().scaled(0.5, 0.5).toFloat64());
|
||||
builder2.pushClipRect(const ui.Rect.fromLTRB(10, 10, 300, 300));
|
||||
builder2.addPicture(ui.Offset.zero, picture1);
|
||||
builder2.pop();
|
||||
builder2.pop();
|
||||
builder2.pop();
|
||||
|
||||
final DomElement contentAfterScale = builder2.build().webOnlyRootElement!;
|
||||
final DomCanvasElement canvas2 = contentAfterScale.querySelector('canvas')! as DomCanvasElement;
|
||||
// Although we are drawing same picture, due to scaling the new canvas
|
||||
// should have fewer pixels.
|
||||
expect(canvas2.width! < unscaledWidth, isTrue);
|
||||
expect(canvas2.height! < unscaledHeight, isTrue);
|
||||
});
|
||||
|
||||
test('Canvas should allocate more pixels when zoomed in', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final ui.Picture picture1 = _drawPicture();
|
||||
builder.pushClipRect(const ui.Rect.fromLTRB(10, 10, 300, 300));
|
||||
builder.addPicture(ui.Offset.zero, picture1);
|
||||
builder.pop();
|
||||
|
||||
final DomElement content = builder.build().webOnlyRootElement!;
|
||||
final DomCanvasElement canvas = content.querySelector('canvas')! as DomCanvasElement;
|
||||
final int unscaledWidth = canvas.width!.toInt();
|
||||
final int unscaledHeight = canvas.height!.toInt();
|
||||
|
||||
// Force update to scene which will utilize reuse code path.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.pushOffset(0, 0);
|
||||
builder2.pushTransform(Matrix4.identity().scaled(2, 2).toFloat64());
|
||||
builder2.pushClipRect(const ui.Rect.fromLTRB(10, 10, 300, 300));
|
||||
builder2.addPicture(ui.Offset.zero, picture1);
|
||||
builder2.pop();
|
||||
builder2.pop();
|
||||
builder2.pop();
|
||||
|
||||
final DomElement contentAfterScale = builder2.build().webOnlyRootElement!;
|
||||
final DomCanvasElement canvas2 = contentAfterScale.querySelector('canvas')! as DomCanvasElement;
|
||||
// Although we are drawing same picture, due to scaling the new canvas
|
||||
// should have more pixels.
|
||||
expect(canvas2.width! > unscaledWidth, isTrue);
|
||||
expect(canvas2.height! > unscaledHeight, isTrue);
|
||||
});
|
||||
|
||||
test('Should recycle canvas once', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final ui.Picture picture1 = _drawPicture();
|
||||
final ui.ClipRectEngineLayer oldLayer = builder.pushClipRect(
|
||||
const ui.Rect.fromLTRB(10, 10, 300, 300),
|
||||
);
|
||||
builder.addPicture(ui.Offset.zero, picture1);
|
||||
builder.pop();
|
||||
builder.build();
|
||||
|
||||
// Force update to scene which will utilize reuse code path.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
final ui.ClipRectEngineLayer oldLayer2 = builder2.pushClipRect(
|
||||
const ui.Rect.fromLTRB(5, 10, 300, 300),
|
||||
oldLayer: oldLayer,
|
||||
);
|
||||
builder2.addPicture(ui.Offset.zero, _drawEmptyPicture());
|
||||
builder2.pop();
|
||||
|
||||
final DomElement contentAfterReuse = builder2.build().webOnlyRootElement!;
|
||||
expect(contentAfterReuse, isNotNull);
|
||||
|
||||
final SurfaceSceneBuilder builder3 = SurfaceSceneBuilder();
|
||||
builder3.pushClipRect(const ui.Rect.fromLTRB(25, 10, 300, 300), oldLayer: oldLayer2);
|
||||
builder3.addPicture(ui.Offset.zero, _drawEmptyPicture());
|
||||
builder3.pop();
|
||||
// This build will crash if canvas gets recycled twice.
|
||||
final DomElement contentAfterReuse2 = builder3.build().webOnlyRootElement!;
|
||||
expect(contentAfterReuse2, isNotNull);
|
||||
});
|
||||
}
|
||||
|
||||
typedef TestLayerBuilder =
|
||||
ui.EngineLayer Function(ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer);
|
||||
typedef ExpectedHtmlGetter = String Function();
|
||||
|
||||
void testLayerLifeCycle(TestLayerBuilder layerBuilder, ExpectedHtmlGetter expectedHtmlGetter) {
|
||||
// Force scene builder to start from scratch. This guarantees that the first
|
||||
// scene starts from the "build" phase.
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
|
||||
// Build: builds a brand new layer.
|
||||
SurfaceSceneBuilder sceneBuilder = SurfaceSceneBuilder();
|
||||
final ui.EngineLayer layer1 = layerBuilder(sceneBuilder, null);
|
||||
final Type surfaceType = layer1.runtimeType;
|
||||
sceneBuilder.pop();
|
||||
|
||||
SceneTester tester = SceneTester(sceneBuilder.build());
|
||||
tester.expectSceneHtml(expectedHtmlGetter());
|
||||
|
||||
PersistedSurface findSurface() {
|
||||
return enumerateSurfaces().where((PersistedSurface s) => s.runtimeType == surfaceType).single;
|
||||
}
|
||||
|
||||
final PersistedSurface surface1 = findSurface();
|
||||
final DomElement surfaceElement1 = surface1.rootElement!;
|
||||
|
||||
// Retain: reuses a layer as is along with its DOM elements.
|
||||
sceneBuilder = SurfaceSceneBuilder();
|
||||
sceneBuilder.addRetained(layer1);
|
||||
|
||||
tester = SceneTester(sceneBuilder.build());
|
||||
tester.expectSceneHtml(expectedHtmlGetter());
|
||||
|
||||
final PersistedSurface surface2 = findSurface();
|
||||
final DomElement surfaceElement2 = surface2.rootElement!;
|
||||
|
||||
expect(surface2, same(surface1));
|
||||
expect(surfaceElement2, same(surfaceElement1));
|
||||
|
||||
// Reuse: reuses a layer's DOM elements by matching it.
|
||||
sceneBuilder = SurfaceSceneBuilder();
|
||||
final ui.EngineLayer layer3 = layerBuilder(sceneBuilder, layer1);
|
||||
sceneBuilder.pop();
|
||||
expect(layer3, isNot(same(layer1)));
|
||||
tester = SceneTester(sceneBuilder.build());
|
||||
tester.expectSceneHtml(expectedHtmlGetter());
|
||||
|
||||
final PersistedSurface surface3 = findSurface();
|
||||
expect(surface3, same(layer3));
|
||||
final DomElement surfaceElement3 = surface3.rootElement!;
|
||||
expect(surface3, isNot(same(surface2)));
|
||||
expect(surfaceElement3, isNotNull);
|
||||
expect(surfaceElement3, same(surfaceElement2));
|
||||
|
||||
// Recycle: discards all the layers.
|
||||
sceneBuilder = SurfaceSceneBuilder();
|
||||
tester = SceneTester(sceneBuilder.build());
|
||||
tester.expectSceneHtml('<flt-scene></flt-scene>');
|
||||
|
||||
expect(surface3.rootElement, isNull); // offset3 should be recycled.
|
||||
|
||||
// Retain again: the framework should be able to request that a layer is added
|
||||
// as retained even after it has been recycled. In this case the
|
||||
// engine would "rehydrate" the layer with new DOM elements.
|
||||
sceneBuilder = SurfaceSceneBuilder();
|
||||
sceneBuilder.addRetained(layer3);
|
||||
tester = SceneTester(sceneBuilder.build());
|
||||
tester.expectSceneHtml(expectedHtmlGetter());
|
||||
expect(surface3.rootElement, isNotNull); // offset3 should be rehydrated.
|
||||
|
||||
// Make sure we clear retained surface list.
|
||||
expect(retainedSurfaces, isEmpty);
|
||||
}
|
||||
|
||||
class MockPersistedPicture extends PersistedPicture {
|
||||
factory MockPersistedPicture() {
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
// Use the largest cull rect so that layer clips are effective. The tests
|
||||
// rely on this.
|
||||
recorder.beginRecording(ui.Rect.largest).drawPaint(SurfacePaint());
|
||||
return MockPersistedPicture._(recorder.endRecording());
|
||||
}
|
||||
|
||||
MockPersistedPicture._(EnginePicture picture) : super(0, 0, picture, 0);
|
||||
|
||||
int retainCount = 0;
|
||||
int buildCount = 0;
|
||||
int updateCount = 0;
|
||||
int applyPaintCount = 0;
|
||||
|
||||
final BitmapCanvas _fakeCanvas = BitmapCanvas(
|
||||
const ui.Rect.fromLTRB(0, 0, 10, 10),
|
||||
RenderStrategy(),
|
||||
);
|
||||
|
||||
@override
|
||||
EngineCanvas get canvas {
|
||||
return _fakeCanvas;
|
||||
}
|
||||
|
||||
@override
|
||||
double matchForUpdate(PersistedPicture existingSurface) {
|
||||
return identical(existingSurface.picture, picture) ? 0.0 : 1.0;
|
||||
}
|
||||
|
||||
@override
|
||||
Matrix4 get localTransformInverse => Matrix4.identity();
|
||||
|
||||
@override
|
||||
void build() {
|
||||
super.build();
|
||||
buildCount++;
|
||||
}
|
||||
|
||||
@override
|
||||
void retain() {
|
||||
super.retain();
|
||||
retainCount++;
|
||||
}
|
||||
|
||||
@override
|
||||
void applyPaint(EngineCanvas? oldCanvas) {
|
||||
applyPaintCount++;
|
||||
}
|
||||
|
||||
@override
|
||||
void update(PersistedPicture oldSurface) {
|
||||
super.update(oldSurface);
|
||||
updateCount++;
|
||||
}
|
||||
|
||||
@override
|
||||
int get bitmapPixelCount => 0;
|
||||
}
|
||||
|
||||
/// Draw 4 circles within 50, 50, 120, 120 bounds
|
||||
ui.Picture _drawPicture() {
|
||||
const double offsetX = 50;
|
||||
const double offsetY = 50;
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(const ui.Rect.fromLTRB(0, 0, 400, 400));
|
||||
final ui.Shader gradient = ui.Gradient.radial(const ui.Offset(100, 100), 50, const <ui.Color>[
|
||||
ui.Color.fromARGB(255, 0, 0, 0),
|
||||
ui.Color.fromARGB(255, 0, 0, 255),
|
||||
]);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(offsetX + 10, offsetY + 10),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..shader = gradient,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(offsetX + 60, offsetY + 10),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = const ui.Color.fromRGBO(255, 0, 0, 1),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(offsetX + 10, offsetY + 60),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = const ui.Color.fromRGBO(0, 255, 0, 1),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(offsetX + 60, offsetY + 60),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = const ui.Color.fromRGBO(0, 0, 255, 1),
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
EnginePicture _drawEmptyPicture() {
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
recorder.beginRecording(const ui.Rect.fromLTRB(0, 0, 400, 400));
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
EnginePicture _drawPathImagePath() {
|
||||
const double offsetX = 50;
|
||||
const double offsetY = 50;
|
||||
final EnginePictureRecorder recorder = EnginePictureRecorder();
|
||||
final RecordingCanvas canvas = recorder.beginRecording(const ui.Rect.fromLTRB(0, 0, 400, 400));
|
||||
final ui.Shader gradient = ui.Gradient.radial(const ui.Offset(100, 100), 50, const <ui.Color>[
|
||||
ui.Color.fromARGB(255, 0, 0, 0),
|
||||
ui.Color.fromARGB(255, 0, 0, 255),
|
||||
]);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(offsetX + 10, offsetY + 10),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..shader = gradient,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(offsetX + 60, offsetY + 10),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = const ui.Color.fromRGBO(255, 0, 0, 1),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(offsetX + 10, offsetY + 60),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = const ui.Color.fromRGBO(0, 255, 0, 1),
|
||||
);
|
||||
canvas.drawImage(createTestImage(), ui.Offset.zero, SurfacePaint());
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(offsetX + 10, offsetY + 10),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..shader = gradient,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(offsetX + 60, offsetY + 60),
|
||||
10,
|
||||
SurfacePaint()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = const ui.Color.fromRGBO(0, 0, 255, 1),
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
HtmlImage createTestImage({int width = 100, int height = 50}) {
|
||||
final DomCanvasElement canvas = createDomCanvasElement(width: width, height: height);
|
||||
final DomCanvasRenderingContext2D ctx = canvas.context2D;
|
||||
ctx.fillStyle = '#E04040';
|
||||
ctx.fillRect(0, 0, 33, 50);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#40E080';
|
||||
ctx.fillRect(33, 0, 33, 50);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#2040E0';
|
||||
ctx.fillRect(66, 0, 33, 50);
|
||||
ctx.fill();
|
||||
final DomHTMLImageElement imageElement = createDomHTMLImageElement();
|
||||
imageElement.src = js_util.callMethod<String>(canvas, 'toDataURL', <dynamic>[]);
|
||||
return HtmlImage(imageElement, width, height);
|
||||
}
|
||||
|
||||
class SceneTester {
|
||||
SceneTester(this.scene);
|
||||
|
||||
final SurfaceScene scene;
|
||||
|
||||
void expectSceneHtml(String expectedHtml) {
|
||||
expect(scene.webOnlyRootElement, hasHtml(expectedHtml));
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// 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/html/shaders/normalized_gradient.dart';
|
||||
import 'package:ui/ui.dart' as ui hide window;
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
group('Shader Normalized Gradient', () {
|
||||
test('3 stop at start', () {
|
||||
final NormalizedGradient gradient = NormalizedGradient(
|
||||
const <ui.Color>[ui.Color(0xFF000000), ui.Color(0xFFFF7f3f)],
|
||||
stops: <double>[0.0, 0.5],
|
||||
);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.25);
|
||||
assert(res == 0xFF7f3f1f);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
res = _computeColorAt(gradient, 0.7);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
});
|
||||
|
||||
test('3 stop at end', () {
|
||||
final NormalizedGradient gradient = NormalizedGradient(
|
||||
const <ui.Color>[ui.Color(0xFF000000), ui.Color(0xFFFF7f3f)],
|
||||
stops: <double>[0.5, 1.0],
|
||||
);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.25);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.75);
|
||||
assert(res == 0xFF7f3f1f);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
});
|
||||
|
||||
test('4 stop', () {
|
||||
final NormalizedGradient gradient = NormalizedGradient(
|
||||
const <ui.Color>[ui.Color(0xFF000000), ui.Color(0xFFFF7f3f)],
|
||||
stops: <double>[0.25, 0.5],
|
||||
);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.25);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.4);
|
||||
assert(res == 0xFF994c25);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
res = _computeColorAt(gradient, 0.75);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
});
|
||||
|
||||
test('5 stop', () {
|
||||
final NormalizedGradient gradient = NormalizedGradient(
|
||||
const <ui.Color>[
|
||||
ui.Color(0x10000000),
|
||||
ui.Color(0x20FF0000),
|
||||
ui.Color(0x4000FF00),
|
||||
ui.Color(0x800000FF),
|
||||
ui.Color(0xFFFFFFFF),
|
||||
],
|
||||
stops: <double>[0.0, 0.1, 0.2, 0.5, 1.0],
|
||||
);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0x10000000);
|
||||
res = _computeColorAt(gradient, 0.05);
|
||||
assert(res == 0x187f0000);
|
||||
res = _computeColorAt(gradient, 0.1);
|
||||
assert(res == 0x20ff0000);
|
||||
res = _computeColorAt(gradient, 0.15);
|
||||
assert(res == 0x307f7f00);
|
||||
res = _computeColorAt(gradient, 0.2);
|
||||
assert(res == 0x4000ff00);
|
||||
res = _computeColorAt(gradient, 0.4);
|
||||
assert(res == 0x6a0054a9);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0x800000fe);
|
||||
res = _computeColorAt(gradient, 0.9);
|
||||
assert(res == 0xe5ccccff);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xffffffff);
|
||||
});
|
||||
|
||||
test('2 stops at ends', () {
|
||||
final NormalizedGradient gradient = NormalizedGradient(const <ui.Color>[
|
||||
ui.Color(0x00000000),
|
||||
ui.Color(0xFFFFFFFF),
|
||||
]);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xFFFFFFFF);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0x7f7f7f7f);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
int _computeColorAt(NormalizedGradient gradient, double t) {
|
||||
int i = 0;
|
||||
while (t > gradient.thresholdAt(i + 1)) {
|
||||
++i;
|
||||
}
|
||||
final double r = t * gradient.scaleAt(i * 4) + gradient.biasAt(i * 4);
|
||||
final double g = t * gradient.scaleAt(i * 4 + 1) + gradient.biasAt(i * 4 + 1);
|
||||
final double b = t * gradient.scaleAt(i * 4 + 2) + gradient.biasAt(i * 4 + 2);
|
||||
final double a = t * gradient.scaleAt(i * 4 + 3) + gradient.biasAt(i * 4 + 3);
|
||||
int val = 0;
|
||||
val |= (a * 0xFF).toInt() & 0xFF;
|
||||
val <<= 8;
|
||||
val |= (r * 0xFF).toInt() & 0xFF;
|
||||
val <<= 8;
|
||||
val |= (g * 0xFF).toInt() & 0xFF;
|
||||
val <<= 8;
|
||||
val |= (b * 0xFF).toInt() & 0xFF;
|
||||
return val;
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
// 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 '../../../common/test_initialization.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
const String mat2Sample = 'mat2(1.1, 2.1, 1.2, 2.2)';
|
||||
const String mat3Sample =
|
||||
'mat3(1.1, 2.1, 3.1, // first column (not row!)\n'
|
||||
'1.2, 2.2, 3.2, // second column\n'
|
||||
'1.3, 2.3, 3.3 // third column\n'
|
||||
')';
|
||||
const String mat4Sample =
|
||||
'mat3(1.1, 2.1, 3.1, 4.1,\n'
|
||||
'1.2, 2.2, 3.2, 4.2,\n'
|
||||
'1.3, 2.3, 3.3, 4.3,\n'
|
||||
'1.4, 2.4, 3.4, 4.4,\n'
|
||||
')';
|
||||
|
||||
setUpAll(() async {
|
||||
await bootstrapAndRunApp();
|
||||
});
|
||||
|
||||
group('Shader Declarations', () {
|
||||
test('Constant declaration WebGL1', () {
|
||||
final ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl1);
|
||||
builder.addConst(ShaderType.kBool, 'false');
|
||||
builder.addConst(ShaderType.kInt, '0');
|
||||
builder.addConst(ShaderType.kFloat, '1.0');
|
||||
builder.addConst(ShaderType.kBVec2, 'bvec2(false, false)');
|
||||
builder.addConst(ShaderType.kBVec3, 'bvec3(false, false, true)');
|
||||
builder.addConst(ShaderType.kBVec4, 'bvec4(true, true, false, false)');
|
||||
builder.addConst(ShaderType.kIVec2, 'ivec2(1, 2)');
|
||||
builder.addConst(ShaderType.kIVec3, 'ivec3(1, 2, 3)');
|
||||
builder.addConst(ShaderType.kIVec4, 'ivec4(1, 2, 3, 4)');
|
||||
builder.addConst(ShaderType.kVec2, 'vec2(1.0, 2.0)');
|
||||
builder.addConst(ShaderType.kVec3, 'vec3(1.0, 2.0, 3.0)');
|
||||
builder.addConst(ShaderType.kVec4, 'vec4(1.0, 2.0, 3.0, 4.0)');
|
||||
builder.addConst(ShaderType.kMat2, mat2Sample);
|
||||
builder.addConst(ShaderType.kMat2, mat2Sample, name: 'transform1');
|
||||
builder.addConst(ShaderType.kMat3, mat3Sample);
|
||||
builder.addConst(ShaderType.kMat4, mat4Sample);
|
||||
expect(
|
||||
builder.build(),
|
||||
'const bool c_0 = false;\n'
|
||||
'const int c_1 = 0;\n'
|
||||
'const float c_2 = 1.0;\n'
|
||||
'const bvec2 c_3 = bvec2(false, false);\n'
|
||||
'const bvec3 c_4 = bvec3(false, false, true);\n'
|
||||
'const bvec4 c_5 = bvec4(true, true, false, false);\n'
|
||||
'const ivec2 c_6 = ivec2(1, 2);\n'
|
||||
'const ivec3 c_7 = ivec3(1, 2, 3);\n'
|
||||
'const ivec4 c_8 = ivec4(1, 2, 3, 4);\n'
|
||||
'const vec2 c_9 = vec2(1.0, 2.0);\n'
|
||||
'const vec3 c_10 = vec3(1.0, 2.0, 3.0);\n'
|
||||
'const vec4 c_11 = vec4(1.0, 2.0, 3.0, 4.0);\n'
|
||||
'const mat2 c_12 = $mat2Sample;\n'
|
||||
'const mat2 transform1 = $mat2Sample;\n'
|
||||
'const mat3 c_13 = $mat3Sample;\n'
|
||||
'const mat4 c_14 = $mat4Sample;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('Constant declaration WebGL2', () {
|
||||
final ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2);
|
||||
builder.addConst(ShaderType.kBool, 'false');
|
||||
builder.addConst(ShaderType.kInt, '0');
|
||||
builder.addConst(ShaderType.kFloat, '1.0');
|
||||
builder.addConst(ShaderType.kBVec2, 'bvec2(false, false)');
|
||||
builder.addConst(ShaderType.kBVec3, 'bvec3(false, false, true)');
|
||||
builder.addConst(ShaderType.kBVec4, 'bvec4(true, true, false, false)');
|
||||
builder.addConst(ShaderType.kIVec2, 'ivec2(1, 2)');
|
||||
builder.addConst(ShaderType.kIVec3, 'ivec3(1, 2, 3)');
|
||||
builder.addConst(ShaderType.kIVec4, 'ivec4(1, 2, 3, 4)');
|
||||
builder.addConst(ShaderType.kVec2, 'vec2(1.0, 2.0)');
|
||||
builder.addConst(ShaderType.kVec3, 'vec3(1.0, 2.0, 3.0)');
|
||||
builder.addConst(ShaderType.kVec4, 'vec4(1.0, 2.0, 3.0, 4.0)');
|
||||
builder.addConst(ShaderType.kMat2, mat2Sample);
|
||||
builder.addConst(ShaderType.kMat2, mat2Sample, name: 'transform2');
|
||||
builder.addConst(ShaderType.kMat3, mat3Sample);
|
||||
builder.addConst(ShaderType.kMat4, mat4Sample);
|
||||
expect(
|
||||
builder.build(),
|
||||
'#version 300 es\n'
|
||||
'const bool c_0 = false;\n'
|
||||
'const int c_1 = 0;\n'
|
||||
'const float c_2 = 1.0;\n'
|
||||
'const bvec2 c_3 = bvec2(false, false);\n'
|
||||
'const bvec3 c_4 = bvec3(false, false, true);\n'
|
||||
'const bvec4 c_5 = bvec4(true, true, false, false);\n'
|
||||
'const ivec2 c_6 = ivec2(1, 2);\n'
|
||||
'const ivec3 c_7 = ivec3(1, 2, 3);\n'
|
||||
'const ivec4 c_8 = ivec4(1, 2, 3, 4);\n'
|
||||
'const vec2 c_9 = vec2(1.0, 2.0);\n'
|
||||
'const vec3 c_10 = vec3(1.0, 2.0, 3.0);\n'
|
||||
'const vec4 c_11 = vec4(1.0, 2.0, 3.0, 4.0);\n'
|
||||
'const mat2 c_12 = $mat2Sample;\n'
|
||||
'const mat2 transform2 = $mat2Sample;\n'
|
||||
'const mat3 c_13 = $mat3Sample;\n'
|
||||
'const mat4 c_14 = $mat4Sample;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('Attribute declaration WebGL1', () {
|
||||
final ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl1);
|
||||
builder.addIn(ShaderType.kVec4, name: 'position');
|
||||
builder.addIn(ShaderType.kVec4);
|
||||
expect(
|
||||
builder.build(),
|
||||
'attribute vec4 position;\n'
|
||||
'attribute vec4 attr_0;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('in declaration WebGL1', () {
|
||||
final ShaderBuilder builder = ShaderBuilder.fragment(WebGLVersion.webgl1);
|
||||
builder.addIn(ShaderType.kVec4, name: 'position');
|
||||
builder.addIn(ShaderType.kVec4);
|
||||
expect(
|
||||
builder.build(),
|
||||
'varying vec4 position;\n'
|
||||
'varying vec4 attr_0;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('Attribute declaration WebGL2', () {
|
||||
final ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2);
|
||||
builder.addIn(ShaderType.kVec4, name: 'position');
|
||||
builder.addIn(ShaderType.kVec4);
|
||||
expect(
|
||||
builder.build(),
|
||||
'#version 300 es\n'
|
||||
'in vec4 position;\n'
|
||||
'in vec4 attr_0;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('Uniform declaration WebGL1', () {
|
||||
final ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl1);
|
||||
final ShaderDeclaration variable = builder.addUniform(ShaderType.kVec4, name: 'v1');
|
||||
expect(variable.name, 'v1');
|
||||
expect(variable.dataType, ShaderType.kVec4);
|
||||
expect(variable.storage, ShaderStorageQualifier.kUniform);
|
||||
builder.addUniform(ShaderType.kVec4);
|
||||
expect(
|
||||
builder.build(),
|
||||
'uniform vec4 v1;\n'
|
||||
'uniform vec4 uni_0;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('Uniform declaration WebGL2', () {
|
||||
final ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2);
|
||||
final ShaderDeclaration variable = builder.addUniform(ShaderType.kVec4, name: 'v1');
|
||||
expect(variable.name, 'v1');
|
||||
expect(variable.dataType, ShaderType.kVec4);
|
||||
expect(variable.storage, ShaderStorageQualifier.kUniform);
|
||||
builder.addUniform(ShaderType.kVec4);
|
||||
expect(
|
||||
builder.build(),
|
||||
'#version 300 es\n'
|
||||
'uniform vec4 v1;\n'
|
||||
'uniform vec4 uni_0;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('Float precision', () {
|
||||
final ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2);
|
||||
builder.floatPrecision = ShaderPrecision.kLow;
|
||||
builder.addUniform(ShaderType.kFloat, name: 'f1');
|
||||
expect(
|
||||
builder.build(),
|
||||
'#version 300 es\n'
|
||||
'precision lowp float;\n'
|
||||
'uniform float f1;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('Integer precision', () {
|
||||
final ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2);
|
||||
builder.integerPrecision = ShaderPrecision.kLow;
|
||||
builder.addUniform(ShaderType.kInt, name: 'i1');
|
||||
expect(
|
||||
builder.build(),
|
||||
'#version 300 es\n'
|
||||
'precision lowp int;\n'
|
||||
'uniform int i1;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('Method', () {
|
||||
final ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2);
|
||||
builder.floatPrecision = ShaderPrecision.kMedium;
|
||||
final ShaderDeclaration variable = builder.addUniform(ShaderType.kFloat, name: 'f1');
|
||||
final ShaderMethod m = builder.addMethod('main');
|
||||
m.addStatement('f1 = 5.0;');
|
||||
expect(
|
||||
builder.build(),
|
||||
'#version 300 es\n'
|
||||
'precision mediump float;\n'
|
||||
'uniform float ${variable.name};\n'
|
||||
'void main() {\n'
|
||||
' f1 = 5.0;\n'
|
||||
'}\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,502 +0,0 @@
|
||||
// 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';
|
||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
group('Surface', () {
|
||||
setUpAll(() async {
|
||||
await bootstrapAndRunApp(withImplicitView: true);
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
});
|
||||
|
||||
test('debugAssertSurfaceState produces a human-readable message', () {
|
||||
final SceneBuilder builder = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer = builder.pushOpacity(100) as PersistedOpacity;
|
||||
try {
|
||||
debugAssertSurfaceState(
|
||||
opacityLayer,
|
||||
PersistedSurfaceState.active,
|
||||
PersistedSurfaceState.pendingRetention,
|
||||
);
|
||||
fail('Expected $PersistedSurfaceException');
|
||||
} on PersistedSurfaceException catch (exception) {
|
||||
expect(
|
||||
'$exception',
|
||||
'PersistedOpacity: is in an unexpected state.\n'
|
||||
'Expected one of: PersistedSurfaceState.active, PersistedSurfaceState.pendingRetention\n'
|
||||
'But was: PersistedSurfaceState.created',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('is created', () {
|
||||
final SceneBuilder builder = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer = builder.pushOpacity(100) as PersistedOpacity;
|
||||
builder.pop();
|
||||
|
||||
expect(opacityLayer, isNotNull);
|
||||
expect(opacityLayer.rootElement, isNull);
|
||||
expect(opacityLayer.isCreated, isTrue);
|
||||
|
||||
builder.build();
|
||||
|
||||
expect(opacityLayer.rootElement!.tagName.toLowerCase(), 'flt-opacity');
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
});
|
||||
|
||||
test('is released', () {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer = builder1.pushOpacity(100) as PersistedOpacity;
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
|
||||
SceneBuilder().build();
|
||||
expect(opacityLayer.isReleased, isTrue);
|
||||
expect(opacityLayer.rootElement, isNull);
|
||||
});
|
||||
|
||||
test('discarding is recursive', () {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer = builder1.pushOpacity(100) as PersistedOpacity;
|
||||
final PersistedTransform transformLayer =
|
||||
builder1.pushTransform(Matrix4.identity().toFloat64()) as PersistedTransform;
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
expect(transformLayer.isActive, isTrue);
|
||||
|
||||
SceneBuilder().build();
|
||||
expect(opacityLayer.isReleased, isTrue);
|
||||
expect(transformLayer.isReleased, isTrue);
|
||||
expect(opacityLayer.rootElement, isNull);
|
||||
expect(transformLayer.rootElement, isNull);
|
||||
});
|
||||
|
||||
test('is updated', () {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer1 = builder1.pushOpacity(100) as PersistedOpacity;
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
expect(opacityLayer1.isActive, isTrue);
|
||||
final DomElement element = opacityLayer1.rootElement!;
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer2 =
|
||||
builder2.pushOpacity(200, oldLayer: opacityLayer1) as PersistedOpacity;
|
||||
expect(opacityLayer1.isPendingUpdate, isTrue);
|
||||
expect(opacityLayer2.isCreated, isTrue);
|
||||
expect(opacityLayer2.oldLayer, same(opacityLayer1));
|
||||
builder2.pop();
|
||||
|
||||
builder2.build();
|
||||
expect(opacityLayer1.isReleased, isTrue);
|
||||
expect(opacityLayer1.rootElement, isNull);
|
||||
expect(opacityLayer2.isActive, isTrue);
|
||||
expect(opacityLayer2.rootElement, element); // adopts old surface's element
|
||||
expect(opacityLayer2.oldLayer, isNull);
|
||||
});
|
||||
|
||||
test('ignores released surface when updated', () {
|
||||
// Build a surface
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer1 = builder1.pushOpacity(100) as PersistedOpacity;
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
expect(opacityLayer1.isActive, isTrue);
|
||||
final DomElement element = opacityLayer1.rootElement!;
|
||||
|
||||
// Release it
|
||||
SceneBuilder().build();
|
||||
expect(opacityLayer1.isReleased, isTrue);
|
||||
expect(opacityLayer1.rootElement, isNull);
|
||||
|
||||
// Attempt to update it
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer2 =
|
||||
builder2.pushOpacity(200, oldLayer: opacityLayer1) as PersistedOpacity;
|
||||
builder2.pop();
|
||||
expect(opacityLayer1.isReleased, isTrue);
|
||||
expect(opacityLayer2.isCreated, isTrue);
|
||||
|
||||
builder2.build();
|
||||
expect(opacityLayer1.isReleased, isTrue);
|
||||
expect(opacityLayer2.isActive, isTrue);
|
||||
expect(opacityLayer2.rootElement, isNot(equals(element)));
|
||||
});
|
||||
|
||||
// This test creates a situation when an intermediate layer disappears,
|
||||
// causing its child to become a direct child of the common ancestor. This
|
||||
// often happens with opacity layers. When opacity reaches 1.0, the
|
||||
// framework removes that layer (as it is no longer necessary). This test
|
||||
// makes sure we reuse the child layer's DOM nodes. Here's the illustration
|
||||
// of what's happening:
|
||||
//
|
||||
// Frame 1 Frame 2
|
||||
//
|
||||
// A A
|
||||
// | |
|
||||
// B ┌──>C
|
||||
// | │ |
|
||||
// C ────┘ L
|
||||
// |
|
||||
// L
|
||||
//
|
||||
// Layer "L" is a logging layer used to track what would happen to the
|
||||
// child of "C" as it's being dragged around the tree. For example, we
|
||||
// check that the child doesn't get discarded by mistake.
|
||||
test(
|
||||
'reparents DOM element when updated',
|
||||
() {
|
||||
final _LoggingTestSurface logger = _LoggingTestSurface();
|
||||
final SurfaceSceneBuilder builder1 = SurfaceSceneBuilder();
|
||||
final PersistedTransform a1 =
|
||||
builder1.pushTransform(
|
||||
(Matrix4.identity()..scale(EngineFlutterDisplay.instance.browserDevicePixelRatio))
|
||||
.toFloat64(),
|
||||
)
|
||||
as PersistedTransform;
|
||||
final PersistedOpacity b1 = builder1.pushOpacity(100) as PersistedOpacity;
|
||||
final PersistedTransform c1 =
|
||||
builder1.pushTransform(Matrix4.identity().toFloat64()) as PersistedTransform;
|
||||
builder1.debugAddSurface(logger);
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
expect(logger.log, <String>['build', 'createElement', 'apply']);
|
||||
|
||||
final DomElement elementA = a1.rootElement!;
|
||||
final DomElement elementB = b1.rootElement!;
|
||||
final DomElement elementC = c1.rootElement!;
|
||||
|
||||
expect(elementC.parent, elementB);
|
||||
expect(elementB.parent, elementA);
|
||||
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
final PersistedTransform a2 =
|
||||
builder2.pushTransform(
|
||||
(Matrix4.identity()..scale(EngineFlutterDisplay.instance.browserDevicePixelRatio))
|
||||
.toFloat64(),
|
||||
oldLayer: a1,
|
||||
)
|
||||
as PersistedTransform;
|
||||
final PersistedTransform c2 =
|
||||
builder2.pushTransform(Matrix4.identity().toFloat64(), oldLayer: c1)
|
||||
as PersistedTransform;
|
||||
builder2.addRetained(logger);
|
||||
builder2.pop();
|
||||
builder2.pop();
|
||||
|
||||
expect(c1.isPendingUpdate, isTrue);
|
||||
expect(c2.isCreated, isTrue);
|
||||
builder2.build();
|
||||
expect(logger.log, <String>['build', 'createElement', 'apply', 'retain']);
|
||||
expect(c1.isReleased, isTrue);
|
||||
expect(c2.isActive, isTrue);
|
||||
|
||||
expect(a2.rootElement, elementA);
|
||||
expect(b1.rootElement, isNull);
|
||||
expect(c2.rootElement, elementC);
|
||||
|
||||
expect(elementC.parent, elementA);
|
||||
expect(elementB.parent, null);
|
||||
},
|
||||
// This method failed on iOS Safari.
|
||||
// TODO(ferhat): https://github.com/flutter/flutter/issues/60036
|
||||
skip:
|
||||
ui_web.browser.browserEngine == ui_web.BrowserEngine.webkit &&
|
||||
ui_web.browser.operatingSystem == ui_web.OperatingSystem.iOs,
|
||||
);
|
||||
|
||||
test('is retained', () {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer = builder1.pushOpacity(100) as PersistedOpacity;
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
final DomElement element = opacityLayer.rootElement!;
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
builder2.addRetained(opacityLayer);
|
||||
expect(opacityLayer.isPendingRetention, isTrue);
|
||||
|
||||
builder2.build();
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
expect(opacityLayer.rootElement, element);
|
||||
});
|
||||
|
||||
test('revives released surface when retained', () {
|
||||
final SurfaceSceneBuilder builder1 = SurfaceSceneBuilder();
|
||||
final PersistedOpacity opacityLayer = builder1.pushOpacity(100) as PersistedOpacity;
|
||||
final _LoggingTestSurface logger = _LoggingTestSurface();
|
||||
builder1.debugAddSurface(logger);
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
expect(logger.log, <String>['build', 'createElement', 'apply']);
|
||||
final DomElement element = opacityLayer.rootElement!;
|
||||
|
||||
SceneBuilder().build();
|
||||
expect(opacityLayer.isReleased, isTrue);
|
||||
expect(opacityLayer.rootElement, isNull);
|
||||
expect(logger.log, <String>['build', 'createElement', 'apply', 'discard']);
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
builder2.addRetained(opacityLayer);
|
||||
expect(opacityLayer.isCreated, isTrue); // revived
|
||||
expect(logger.log, <String>['build', 'createElement', 'apply', 'discard', 'revive']);
|
||||
|
||||
builder2.build();
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
expect(opacityLayer.rootElement, isNot(equals(element)));
|
||||
});
|
||||
|
||||
test('reviving is recursive', () {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer = builder1.pushOpacity(100) as PersistedOpacity;
|
||||
final PersistedTransform transformLayer =
|
||||
builder1.pushTransform(Matrix4.identity().toFloat64()) as PersistedTransform;
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
expect(transformLayer.isActive, isTrue);
|
||||
final DomElement opacityElement = opacityLayer.rootElement!;
|
||||
final DomElement transformElement = transformLayer.rootElement!;
|
||||
|
||||
SceneBuilder().build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
builder2.addRetained(opacityLayer);
|
||||
expect(opacityLayer.isCreated, isTrue); // revived
|
||||
expect(transformLayer.isCreated, isTrue); // revived
|
||||
|
||||
builder2.build();
|
||||
expect(opacityLayer.isActive, isTrue);
|
||||
expect(transformLayer.isActive, isTrue);
|
||||
expect(opacityLayer.rootElement, isNot(equals(opacityElement)));
|
||||
expect(transformLayer.rootElement, isNot(equals(transformElement)));
|
||||
});
|
||||
|
||||
// This test creates a situation when a retained layer is moved to another
|
||||
// parent. We want to make sure that we move the retained layer's elements
|
||||
// without rebuilding from scratch. No new elements are created in this
|
||||
// situation.
|
||||
//
|
||||
// Here's an illustrated example where layer C is reparented onto B along
|
||||
// with D:
|
||||
//
|
||||
// Frame 1 Frame 2
|
||||
//
|
||||
// A A
|
||||
// ╱ ╲ |
|
||||
// B C ──┐ B
|
||||
// | │ |
|
||||
// D └──>C
|
||||
// |
|
||||
// D
|
||||
test('reparents DOM elements when retained', () {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final PersistedOpacity a1 = builder1.pushOpacity(10) as PersistedOpacity;
|
||||
final PersistedOpacity b1 = builder1.pushOpacity(20) as PersistedOpacity;
|
||||
builder1.pop();
|
||||
final PersistedOpacity c1 = builder1.pushOpacity(30) as PersistedOpacity;
|
||||
final PersistedOpacity d1 = builder1.pushOpacity(40) as PersistedOpacity;
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final DomElement elementA = a1.rootElement!;
|
||||
final DomElement elementB = b1.rootElement!;
|
||||
final DomElement elementC = c1.rootElement!;
|
||||
final DomElement elementD = d1.rootElement!;
|
||||
|
||||
expect(elementB.parent, elementA);
|
||||
expect(elementC.parent, elementA);
|
||||
expect(elementD.parent, elementC);
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
final PersistedOpacity a2 = builder2.pushOpacity(10, oldLayer: a1) as PersistedOpacity;
|
||||
final PersistedOpacity b2 = builder2.pushOpacity(20, oldLayer: b1) as PersistedOpacity;
|
||||
builder2.addRetained(c1);
|
||||
builder2.pop();
|
||||
builder2.pop();
|
||||
builder2.build();
|
||||
|
||||
expect(a2.rootElement, elementA);
|
||||
expect(b2.rootElement, elementB);
|
||||
expect(c1.rootElement, elementC);
|
||||
expect(d1.rootElement, elementD);
|
||||
|
||||
expect(
|
||||
<DomElement>[elementD.parent!, elementC.parent!, elementB.parent!],
|
||||
<DomElement>[elementC, elementB, elementA],
|
||||
);
|
||||
});
|
||||
|
||||
test('is updated by matching', () {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer1 = builder1.pushOpacity(100) as PersistedOpacity;
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
expect(opacityLayer1.isActive, isTrue);
|
||||
final DomElement element = opacityLayer1.rootElement!;
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
final PersistedOpacity opacityLayer2 = builder2.pushOpacity(200) as PersistedOpacity;
|
||||
expect(opacityLayer1.isActive, isTrue);
|
||||
expect(opacityLayer2.isCreated, isTrue);
|
||||
builder2.pop();
|
||||
|
||||
builder2.build();
|
||||
expect(opacityLayer1.isReleased, isTrue);
|
||||
expect(opacityLayer1.rootElement, isNull);
|
||||
expect(opacityLayer2.isActive, isTrue);
|
||||
expect(opacityLayer2.rootElement, element); // adopts old surface's element
|
||||
});
|
||||
});
|
||||
|
||||
final Map<String, TestEngineLayerFactory> layerFactories = <String, TestEngineLayerFactory>{
|
||||
'ColorFilterEngineLayer':
|
||||
(SurfaceSceneBuilder builder) =>
|
||||
builder.pushColorFilter(const ColorFilter.mode(Color(0xFFFF0000), BlendMode.srcIn)),
|
||||
'OffsetEngineLayer': (SurfaceSceneBuilder builder) => builder.pushOffset(1, 2),
|
||||
'TransformEngineLayer':
|
||||
(SurfaceSceneBuilder builder) => builder.pushTransform(Matrix4.identity().toFloat64()),
|
||||
'ClipRectEngineLayer':
|
||||
(SurfaceSceneBuilder builder) => builder.pushClipRect(const Rect.fromLTRB(0, 0, 10, 10)),
|
||||
'ClipRRectEngineLayer':
|
||||
(SurfaceSceneBuilder builder) =>
|
||||
builder.pushClipRRect(RRect.fromRectXY(const Rect.fromLTRB(0, 0, 10, 10), 1, 2)),
|
||||
'ClipPathEngineLayer':
|
||||
(SurfaceSceneBuilder builder) =>
|
||||
builder.pushClipPath(Path()..addRect(const Rect.fromLTRB(0, 0, 10, 10))),
|
||||
'OpacityEngineLayer': (SurfaceSceneBuilder builder) => builder.pushOpacity(100),
|
||||
'ImageFilterEngineLayer':
|
||||
(SurfaceSceneBuilder builder) =>
|
||||
builder.pushImageFilter(ImageFilter.blur(sigmaX: 0.1, sigmaY: 0.2)),
|
||||
'BackdropEngineLayer':
|
||||
(SurfaceSceneBuilder builder) =>
|
||||
builder.pushBackdropFilter(ImageFilter.blur(sigmaX: 0.1, sigmaY: 0.2)),
|
||||
// Firefox does not support WebGL in headless mode.
|
||||
if (!isFirefox)
|
||||
'ShaderMaskEngineLayer': (SurfaceSceneBuilder builder) {
|
||||
const List<Color> colors = <Color>[Color(0xFF000000), Color(0xFFFF3C38)];
|
||||
const List<double> stops = <double>[0.0, 1.0];
|
||||
const Rect shaderBounds = Rect.fromLTWH(180, 10, 140, 140);
|
||||
final EngineGradient shader = GradientLinear(
|
||||
Offset(200 - shaderBounds.left, 30 - shaderBounds.top),
|
||||
Offset(320 - shaderBounds.left, 150 - shaderBounds.top),
|
||||
colors,
|
||||
stops,
|
||||
TileMode.clamp,
|
||||
Matrix4.identity().storage,
|
||||
);
|
||||
return builder.pushShaderMask(shader, shaderBounds, BlendMode.srcOver);
|
||||
},
|
||||
};
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/104305
|
||||
for (final MapEntry<String, TestEngineLayerFactory> layerFactory in layerFactories.entries) {
|
||||
test('${layerFactory.key} supports addRetained after being discarded', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushOffset(0, 0);
|
||||
final PersistedSurface oldLayer = layerFactory.value(builder) as PersistedSurface;
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.build();
|
||||
expect(oldLayer.isActive, isTrue);
|
||||
|
||||
// Pump an empty frame so the `oldLayer` is discarded before it's reused.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.build();
|
||||
expect(oldLayer.isReleased, isTrue);
|
||||
|
||||
// At this point the `oldLayer` needs to be revived.
|
||||
final SurfaceSceneBuilder builder3 = SurfaceSceneBuilder();
|
||||
builder3.addRetained(oldLayer);
|
||||
builder3.build();
|
||||
expect(oldLayer.isActive, isTrue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
typedef TestEngineLayerFactory = EngineLayer Function(SurfaceSceneBuilder builder);
|
||||
|
||||
class _LoggingTestSurface extends PersistedContainerSurface {
|
||||
_LoggingTestSurface() : super(null);
|
||||
|
||||
final List<String> log = <String>[];
|
||||
|
||||
@override
|
||||
void build() {
|
||||
log.add('build');
|
||||
super.build();
|
||||
}
|
||||
|
||||
@override
|
||||
void apply() {
|
||||
log.add('apply');
|
||||
}
|
||||
|
||||
@override
|
||||
DomElement createElement() {
|
||||
log.add('createElement');
|
||||
return createDomElement('flt-test-layer');
|
||||
}
|
||||
|
||||
@override
|
||||
void update(_LoggingTestSurface oldSurface) {
|
||||
log.add('update');
|
||||
super.update(oldSurface);
|
||||
}
|
||||
|
||||
@override
|
||||
void adoptElements(covariant PersistedSurface oldSurface) {
|
||||
log.add('adoptElements');
|
||||
super.adoptElements(oldSurface);
|
||||
}
|
||||
|
||||
@override
|
||||
void retain() {
|
||||
log.add('retain');
|
||||
super.retain();
|
||||
}
|
||||
|
||||
@override
|
||||
void discard() {
|
||||
log.add('discard');
|
||||
super.discard();
|
||||
}
|
||||
|
||||
@override
|
||||
void revive() {
|
||||
log.add('revive');
|
||||
super.revive();
|
||||
}
|
||||
|
||||
@override
|
||||
double matchForUpdate(PersistedSurface? existingSurface) {
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
// 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:ui/src/engine.dart';
|
||||
|
||||
/// 50x50 pixel flutter logo image that contains alpha ramps and colors
|
||||
/// specifically to transparency and blending.
|
||||
const String _flutterLogoBase64 =
|
||||
'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAKRlWElm'
|
||||
'TU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgExAAIAAAAg'
|
||||
'AAAAWodpAAQAAAABAAAAegAAAAAAAABIAAAAAQAAAEgAAAABQWRvYmUgUGhvdG9zaG9wIENT'
|
||||
'NiAoTWFjaW50b3NoKQAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAMqADAAQAAAABAAAAMgAA'
|
||||
'AABWBXsWAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEemlUWHRYTUw6Y29tLmFkb2JlLnhtcAAA'
|
||||
'AAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENv'
|
||||
'cmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5'
|
||||
'OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjph'
|
||||
'Ym91dD0iIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94'
|
||||
'YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5j'
|
||||
'b20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnhtcD0i'
|
||||
'aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0i'
|
||||
'aHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8eG1wTU06SW5zdGFu'
|
||||
'Y2VJRD54bXAuaWlkOjMyOERERjc5ODRCRjExRUE5QUE4OEM5NTZDREM5QkUyPC94bXBNTTp'
|
||||
'JbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD54bXAuZGlkOjMyOERERj'
|
||||
'dBODRCRjExRUE5QUE4OEM5NTZDREM5QkUyPC94bXBNTTpEb2N1bWVudElEPgogICAgICAgI'
|
||||
'CA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6MDE4MDExNzQwNzIwNjgxMTgy'
|
||||
'MkFBQjU0OEFBMDMwM0E8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHht'
|
||||
'cE1NOkRlcml2ZWRGcm9tIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICA'
|
||||
'gPHN0UmVmOmluc3RhbmNlSUQ+eG1wLmlpZDowNDgwMTE3NDA3MjA2ODExODIyQUFCNTQ4QU'
|
||||
'EwMzAzQTwvc3RSZWY6aW5zdGFuY2VJRD4KICAgICAgICAgICAgPHN0UmVmOmRvY3VtZW50SU'
|
||||
'Q+eG1wLmRpZDowMTgwMTE3NDA3MjA2ODExODIyQUFCNTQ4QUEwMzAzQTwvc3RSZWY6ZG9jd'
|
||||
'W1lbnRJRD4KICAgICAgICAgPC94bXBNTTpEZXJpdmVkRnJvbT4KICAgICAgICAgPHhtcDpD'
|
||||
'cmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpPC94bXA6Q3JlYXRv'
|
||||
'clRvb2w+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb2'
|
||||
'4+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhP'
|
||||
'gr/+ApQAAAQNUlEQVRoBbVaDYxdRRWemTv3vt2WbrctpRYxtCVU4io1LomElrjYWnbbgtr2'
|
||||
'CbISwGpR0UBUjD8YHyZCNCqIP5GmtLWSAn3SVYqw7PbnFUQFXGOMRUFlW9G2gKalu/veu38'
|
||||
'zfufce3ff275dWtBJ5s7cuTPnnO+cM2d+3pN2urDiVSHEVCFuOpgT35vVIuaLV8Tgtw8Icf'
|
||||
'PZkZgntDggrhNCbhHCuihD9J402Y4OLUulqNy1+iwvFIscaaqRMWqyQRofIxFH2ohpwqjDc'
|
||||
't/OZyyYSTAVHQUtSoVIfPiL7xWu3i2i0KA7PnFmstpaEVMLBnHm1rFHhEbiQZ9PKtl83pPF'
|
||||
'YnB0xVVne2HlKcdz5wgTC62cZLwlGdJUSxXtWnngFItIOhdzj3xeiWIxzrpPVmpAZg4EJjc'
|
||||
'tAcmqazqNxjnJVwEOnKjDhMmuX+/KDRuCoeVXn9EcDf/K0c4cEwYBBmgBjSUJZVbnJiuh9'
|
||||
'1BZ4ZnYREpNucgtPfAMW7VYjHjM7Ldlg1MaJxYaxlnNzVCUP4THrLRTdRiVWVaEcQ54fpu2'
|
||||
'JoTTl9rid+3tBCL8a1d3S1M0/ARAnGWiyEcfjK+xaB0Icg0ZQXGekEoo4V0qdwNEatVR+q8'
|
||||
'8O6kCqZ+Wx0QPD6jgeTop72Xxd26Yx0/xYlJAFmJa4xdZO74kcwIgPpObF//rce3qhSYMAw'
|
||||
'zIpaqsEYSqaE0Kcmso04F/q/fr/uKeE0AQm5OwCCwqvCzn90MzMHEbsijsJ4f1RBuysFCa'
|
||||
'TGUaA0C1bGKjKufFh/Zo111kopAsQXQnTABAvu9IR6OiunX/jocagpiQQv0HDYJkhiS1Jc'
|
||||
'V+Lupe0g71BRg7mNjsbmHnml7t6IswJ3wog9xpggR4Uhh4mFTapTjwCb17xzbblgSJCQa9'
|
||||
'ZvOkIXGy0SkIjihh5+odcKfl6cSeBAQoSrinhYm1qwDi887uHXdbml/7i2MKnYzxBN9eFx'
|
||||
'ArCgqWYBDBpWu2wp0+aAKOTll0m4AdQbBGuZ4DELcCxHfINcXAwIRBhAmdxGQ/ZSAEQogC'
|
||||
'0w861/7Q9dyrGQTNqwkTzxaAEBFAaBOa7zq7dhTIqqJUogk/2XQ6uck+Ie8GH9i7O0oKjI3'
|
||||
'ftfabAPEpE/i00mPFz1IDmZKmUHmua6J4g7O753NMq9hGobDBADRaK7NcaJuNbkLMoaV5gn'
|
||||
'RqFunocGjr4XeuvcXT7heMXyWXIPLMiHk0FEsEsIQXh/F9zq6fX8/9CgWsxYWG4Zy/1zzmz'
|
||||
'n2e6U9VpMPGacIP47vbdqzaAxvCYEX+RtfRd2Jix8IaGq/qdJoteCkBdPCldnNxFO9EiL2'
|
||||
'cmmsDRdrtJIv3X+Rdc/6TQRiQAoj3qPyTGGuMNhhj/7QhCDrz61zlEAjonbwCIGrTOBD4xC'
|
||||
'BsFO3R/T2vCQKupCSiGkpsYcU25NORg7Q9eu6Fg7POu+lOMWN6kxyB65EUWXpNIAmIYlBen'
|
||||
'r/CddRGG4UEAiHUJrvAGmIZ0bQMEGJhiehpZ/Gi94n+nmTxzPZP4zqnr5mGSa7lyE3UDnB'
|
||||
'UiJmtrUJUA+u2NktaySEHPTjVazRrTUvb1ZWjnWz10vzKZi3vp9ULoacxiHpr+ADhYa/1p'
|
||||
'8PD8zpkoWDYNTG/xrEY/5pJRuWx9GNoDBhjl18ul7EJULzFoBBYmyYEwpZ49FG/0pV/T07Z'
|
||||
'h0l+YwlJaolaKmk9VWegHJ1DdHpBTZ1+8Vt+c0eFN5SYXw2GTNREpNJ9P0qJzVgSVFDWA8g'
|
||||
'INASSudPIijUXuNbsojkFrYR1IGrppdZAE0A4HhR4WLkti+XPtxwjWhcMDJwKiEy2sTLlVc'
|
||||
'uyZp5zvxPmSAbC78q/TRtbgiV1DEcHYneU0GgFNACC1IemUCkFEOaoEqctlo9sOZLRGpPo/'
|
||||
'1ers0jG+Njy/HzHmCeUElPh5yE6aZadHg1B4GCklItwU45k08Wy76eDGa3/jei1TDOK9W2'
|
||||
'jQHgyYmIPL/3wnGkqfsJx5EwTxwECRmKJ+nFsCSKJ5gjLFIGIY6kvyT123/4sSOCjPJVcEi'
|
||||
'WW55g4lk63TOjXLnlgttgd7fhAa5OuPgk/fzOBwHCP3b8WBDWkcwLfAULCPSWCSW6Z91jxab'
|
||||
'YEgkTBpmvMKUC5RF7CUa1VtNJ5pkGqFaT+s04ORhvCwY5rm07zjpccxznHhFEV3Wg7bngC0J'
|
||||
'h6GqQxCok43SkRW3mZ7r2/lFoCIAqqIJPtx3K7fOp0QWfoydM/8Xn1S6vVzXNu9ntF75xO0Z'
|
||||
'mNsbRI0mgc7vlJ9WSyjwnF1zbUfJZ3fJfWDk53URmvCQjqjmU1nc/ULUkSNzfQuMLNSGzVlb'
|
||||
'qv+HA6J/y8zTsAwVv8lfHSexGx3zsim4fQPwunqI4JwHXI1wIuj5/RL5eY86f+Otj31c6mTr'
|
||||
'o0mIOcbUcgDAHBZjIZnxLhHYbQe3EeWOLO6NeOWgwQw+iTA3AIkvQb8yLGQJToYAQUjo6ts0'
|
||||
'73bX8guz1JLcEgVkXLtjrK6Q5tSCvz3FHLMpnkQQIlWiZvBWPpisA/duvXf3v7FtEhvp52'
|
||||
'HQPCwmRWyZRB73CNDpwH7PLV/8FqBxFZ97Ryk6gpnaxg5CSkxRyaYoy4ESA28ekOtyckU0E'
|
||||
'UmPqqeOkPYK6rQxMeBxtsKhOmia8k9XFWiRylWlUgbnu6+R8F0GoBH+qe5URaFiUJ91yted'
|
||||
'C+2Kq+HWutMT3KUdNgPbgW+SSW8vpMExFhlkDILzt9D97F84sWu4S2gnrtqnjZ7VKpGyIT0f'
|
||||
'2lQxrJMt5JO2nmCw36FmuAcEJz1/bcL7+C79ibHKM+pLTaDFWTTKPg6uoKvs2+q/p7VsMQvT'
|
||||
'D1DHSukHsBJM0FIgYQ0ldStUTWfMPp+9ntPA7WJBAdogPbCBGvsku/BDG/iHu2oxhDWiRmpI'
|
||||
'AYQoAO5awuqa3iKDFdhXbjTm/fjeDPboCoReNGQaA9fZdp2xgtsGH6fPbmczNGymDRZRhUQn'
|
||||
'UmBKxAWxlBHxxmGmPvcPt6biENi/R0RyBKshQttUtuALvbsB/7NyihyygI0ABjChDImKoRWQ'
|
||||
'FtFSy4s5zAbtvplT6O/mJADIwGBLyOAsEcI2GjRLGkAIl60gY2JCPtPMAWu9IkDBewMMcrMe'
|
||||
'A3YNQK34ab2Qosejri8I+dXT2fpf50ZqfTXbttdwnEvOicaw6Yl+4oi+rLjlUkOBNHRxKYB'
|
||||
'E8si0WT2iAERcZZOrA7dub2daMu8ohq7aKdvlMiRVCdAWCK8ThugyIQSfGe0IL0iUXwkRODo'
|
||||
'WuZnTvL0puyCjr9Iz7QAacVN4Fbnf6eT1JHW8ANSgpiQA6EC6IFq+FyP8BN+n9eEEeiEVnF'
|
||||
'tbuC8BYMUhAQiN6BLgEhzUzHyN6HcvvWEk0K2UWOlPQ2mjIgMeYtA8KXpMzeUyuTpdki2VC'
|
||||
'Jicur/C+3HcWEXQlBDkN7jwDEtdSH5gWdLcgSBGK+Pfd9WBDvRph71REKBzZrB+3hqCICEF'
|
||||
'YQnMAkIFDSnCvD3VsB4okFB5rX4N122A5dlA1v3OuFz0DAAuRaRBd2G811QPBR0Lmc3ayv5'
|
||||
'2VldCe2UeuonUHgLqvNtnkE4hx7zkUw8T3wAdwa2ypOKwYzPoyFiV4Qh7A1qAHDmpMj6DcN'
|
||||
'IH4/52/hmu+f+6hPIMg1iX6DlAEJMW+5DhdDCdciS3Od3pN8wjaeCLKbJdehB+md3alQiF'
|
||||
'NLBPPtwkU4c2zGygJCxoeGcPUqcQam5VuJEPP8gDgsFoi52MNoeKmhfdt0x8q/tLwi8pvO'
|
||||
'e3IonV+TnVPoG+UIkx3GQ+IYQnakOjKVaWoIhL7RTSKH2DbcPcGdUu2FC+yCc42JtkB4zB'
|
||||
'U7hJ4uaAa8XIIwgdEAU4EMg+KInC/mRtq6LdLEL055VV1535l7/r0errkBVs2EaFCSiKTtD'
|
||||
'AwDgbK4KzZfKYgxJMmXBpTqmmgni0PiPDvvPBk7GyH0TAwsI5TiB5w6xWAY9I8nxVEf7jZN'
|
||||
'TvEWSOdwa1V//P7mPxxotwLziwWsY0EvtI5A+dhfWPqVaSfybGS6RSGjyAP/PCTmf+w2MfuM'
|
||||
'aeI4lkefGKXphDmSfRhXMmAbOXOxZM2AiY+DOKYDbvKtCGBxMGOGXNJ7bGTgKeWPhH82T93'
|
||||
'bdYRAEM0Bub2G/Tgu9a9kDXLJzCqoS3oHD4nfXlCX+J7mkwNCoRHuf9D9+14o7CaAyAEQX'
|
||||
'Sv5VCLeBtiIMBPYLmHoaNyiDcaV4mePlje8Y9G06++5O5HzQ7HIb69d+JLm+if5DB0lKFe'
|
||||
'w5HHJ75JAMEDwIZ5JPjkgoADksdgr9KAc7MeK/DVw8gDCxx4NHgTCsAATNSCsXJwsnw/K2'
|
||||
'z8TlO98Z6wXRkfiYdXe3L35W0RKFAEG6xHXGz/IA+goQfdaTeBH9WY+xBnrsxUYAPFMrDQ'
|
||||
'ZMYwdly7BBETIBJiHsFP+NgRvggWqWMwpI3oBmMj5Vj7nl39xvV/5xrt85/zAt1W4obWHs'
|
||||
'OAvabpq0y1MFQGkARiyBCVypz3IjyD3wgMeQ/nwSLn6lMi5EWIvpocEIALFwHz2fXQ6+YR'
|
||||
'whc40+eOzo7OvwvRchw0JXaZBKTn8RvycrfReJ6ufXizkhcmSaHCKhPWgPEVXSrOMUL/wt1'
|
||||
'33vYQpxTuOq8nrpM8FC5u683dh0SqDP8kxKv+pWYSY0BmcNoSIZAf1wW04Nf0E95dNiMJlg'
|
||||
'KgGu7v94NoLq84FcdUGsIVUsFbq7zguIigcQgC8tKl780eJXJI4eGcvXMIKKst79+7lZaJ9f'
|
||||
'Xeuis0sZKAzPeaNHc2nDoTYJGBoyZD/kINbrWl60Kq/5oJf5YcrH1tStctUNdROJdJuFf'
|
||||
'eSVei7CgCcMZbc5hDm1grvqs15tkbh1jrtMgtEFQrFlH/0o1fY5Q5VeIWniZ9k0ISpOb8'
|
||||
'+IMyJLUM18aJ+blP49BW9w99Z5oUXNg+FWlWMlVV4DJUVSAMtphlC4NcICptHsIG9PNd9T'
|
||||
'xfmCs2XE8AwcTyKbc8ykMMWgZAOfpJ3z2QZZMP59QMhLmSZNPq89O4HNsYt7uPWiTUCJB1x'
|
||||
'y7AENomU+VcA/BKQvBNAjIQmASY2l+srN1+cgDnRxTIwSakoaJQ5SzpiEKAkvzEgRL0m+lS'
|
||||
'3fnQjzn/PQEsKAQCLphgBGGQLUJSTd2onUABCIF9WNrrMvWLTu0QBR+zJwjJub2ENuiAhA'
|
||||
'NiEZtmOvHEg48Hcv24z/ABnGUnniFeh46HxGYCGpDVDCDzD+M0Ll14KkyDuyn3knrfWKia'
|
||||
'xQs2TNtcYC08YwrpFgOiaCVmB1v8ykTbZQnu19/zgSmXs6QjKFHKZD52U6pIBJPoGc+G6i'
|
||||
'koVGq9PFK/5FyISxuA7p7R+7c1vEqGzAusRBQwk2j0mabSSNbzhMgPTsVe7Zx54O85TYz9b'
|
||||
'G7q0QYqx43NQp5J+DyaxgBqC4d9IEn9j8Z8VxRvogo76E5ik7C7gmmjkHXjFDz64oKFxDs5'
|
||||
'rMQ4IqP4fUo0219/tijMXppoFq1LKbmHySy2/PY/v9H50hhEz8KvE0RkS2xhsP8YlvvGZ3S'
|
||||
'yapiT0mk/DqjIsBcr/Atffr8/hCjApAAAAAElFTkSuQmCC';
|
||||
|
||||
HtmlImage createFlutterLogoTestImage() {
|
||||
return HtmlImage(
|
||||
createDomHTMLImageElement()..src = 'data:text/plain;base64,$_flutterLogoBase64',
|
||||
50,
|
||||
50,
|
||||
);
|
||||
}
|
||||
@@ -20,11 +20,6 @@ void main() {
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(setUpTestViewDimensions: false);
|
||||
|
||||
final bool deviceClipRoundsOut = renderer is! HtmlRenderer;
|
||||
runCanvasTests(deviceClipRoundsOut: deviceClipRoundsOut);
|
||||
}
|
||||
|
||||
void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
setUp(() {
|
||||
EngineSemantics.debugResetSemantics();
|
||||
});
|
||||
@@ -150,23 +145,22 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, const ui.Rect.fromLTRB(0, 0, 100, 100));
|
||||
const ui.Rect clipRawBounds = ui.Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
|
||||
const ui.Rect clipExpandedBounds = ui.Rect.fromLTRB(10, 11, 21, 26);
|
||||
final ui.Rect clipDestBounds = deviceClipRoundsOut ? clipExpandedBounds : clipRawBounds;
|
||||
canvas.clipRect(clipRawBounds);
|
||||
|
||||
// Save initial return values for testing restored values
|
||||
final ui.Rect initialLocalBounds = canvas.getLocalClipBounds();
|
||||
final ui.Rect initialDestinationBounds = canvas.getDestinationClipBounds();
|
||||
rectsClose(initialLocalBounds, clipExpandedBounds);
|
||||
rectsClose(initialDestinationBounds, clipDestBounds);
|
||||
rectsClose(initialDestinationBounds, clipExpandedBounds);
|
||||
|
||||
canvas.save();
|
||||
canvas.clipRect(const ui.Rect.fromLTRB(0, 0, 15, 15));
|
||||
// Both clip bounds have changed
|
||||
rectsNotClose(canvas.getLocalClipBounds(), clipExpandedBounds);
|
||||
rectsNotClose(canvas.getDestinationClipBounds(), clipDestBounds);
|
||||
rectsNotClose(canvas.getDestinationClipBounds(), clipExpandedBounds);
|
||||
// Previous return values have not changed
|
||||
rectsClose(initialLocalBounds, clipExpandedBounds);
|
||||
rectsClose(initialDestinationBounds, clipDestBounds);
|
||||
rectsClose(initialDestinationBounds, clipExpandedBounds);
|
||||
canvas.restore();
|
||||
|
||||
// save/restore returned the values to their original values
|
||||
@@ -178,7 +172,7 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
const ui.Rect scaledExpandedBounds = ui.Rect.fromLTRB(5, 5.5, 10.5, 13);
|
||||
rectsClose(canvas.getLocalClipBounds(), scaledExpandedBounds);
|
||||
// Destination bounds are unaffected by transform
|
||||
rectsClose(canvas.getDestinationClipBounds(), clipDestBounds);
|
||||
rectsClose(canvas.getDestinationClipBounds(), clipExpandedBounds);
|
||||
canvas.restore();
|
||||
|
||||
// save/restore returned the values to their original values
|
||||
@@ -191,7 +185,6 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, const ui.Rect.fromLTRB(0, 0, 100, 100));
|
||||
const ui.Rect clipRawBounds = ui.Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
|
||||
const ui.Rect clipExpandedBounds = ui.Rect.fromLTRB(10, 11, 21, 26);
|
||||
final ui.Rect clipDestBounds = deviceClipRoundsOut ? clipExpandedBounds : clipRawBounds;
|
||||
final ui.RRect clip = ui.RRect.fromRectAndRadius(clipRawBounds, const ui.Radius.circular(3));
|
||||
canvas.clipRRect(clip);
|
||||
|
||||
@@ -199,16 +192,16 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
final ui.Rect initialLocalBounds = canvas.getLocalClipBounds();
|
||||
final ui.Rect initialDestinationBounds = canvas.getDestinationClipBounds();
|
||||
rectsClose(initialLocalBounds, clipExpandedBounds);
|
||||
rectsClose(initialDestinationBounds, clipDestBounds);
|
||||
rectsClose(initialDestinationBounds, clipExpandedBounds);
|
||||
|
||||
canvas.save();
|
||||
canvas.clipRect(const ui.Rect.fromLTRB(0, 0, 15, 15));
|
||||
// Both clip bounds have changed
|
||||
rectsNotClose(canvas.getLocalClipBounds(), clipExpandedBounds);
|
||||
rectsNotClose(canvas.getDestinationClipBounds(), clipDestBounds);
|
||||
rectsNotClose(canvas.getDestinationClipBounds(), clipExpandedBounds);
|
||||
// Previous return values have not changed
|
||||
rectsClose(initialLocalBounds, clipExpandedBounds);
|
||||
rectsClose(initialDestinationBounds, clipDestBounds);
|
||||
rectsClose(initialDestinationBounds, clipExpandedBounds);
|
||||
canvas.restore();
|
||||
|
||||
// save/restore returned the values to their original values
|
||||
@@ -220,7 +213,7 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
const ui.Rect scaledExpandedBounds = ui.Rect.fromLTRB(5, 5.5, 10.5, 13);
|
||||
rectsClose(canvas.getLocalClipBounds(), scaledExpandedBounds);
|
||||
// Destination bounds are unaffected by transform
|
||||
rectsClose(canvas.getDestinationClipBounds(), clipDestBounds);
|
||||
rectsClose(canvas.getDestinationClipBounds(), clipExpandedBounds);
|
||||
canvas.restore();
|
||||
|
||||
// save/restore returned the values to their original values
|
||||
@@ -233,7 +226,6 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, const ui.Rect.fromLTRB(0, 0, 100, 100));
|
||||
const ui.Rect clipRawBounds = ui.Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
|
||||
const ui.Rect clipExpandedBounds = ui.Rect.fromLTRB(10, 11, 21, 26);
|
||||
final ui.Rect clipDestBounds = deviceClipRoundsOut ? clipExpandedBounds : clipRawBounds;
|
||||
final ui.Path clip =
|
||||
ui.Path()
|
||||
..addRect(clipRawBounds)
|
||||
@@ -244,16 +236,16 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
final ui.Rect initialLocalBounds = canvas.getLocalClipBounds();
|
||||
final ui.Rect initialDestinationBounds = canvas.getDestinationClipBounds();
|
||||
rectsClose(initialLocalBounds, clipExpandedBounds);
|
||||
rectsClose(initialDestinationBounds, clipDestBounds);
|
||||
rectsClose(initialDestinationBounds, clipExpandedBounds);
|
||||
|
||||
canvas.save();
|
||||
canvas.clipRect(const ui.Rect.fromLTRB(0, 0, 15, 15));
|
||||
// Both clip bounds have changed
|
||||
rectsNotClose(canvas.getLocalClipBounds(), clipExpandedBounds);
|
||||
rectsNotClose(canvas.getDestinationClipBounds(), clipDestBounds);
|
||||
rectsNotClose(canvas.getDestinationClipBounds(), clipExpandedBounds);
|
||||
// Previous return values have not changed
|
||||
rectsClose(initialLocalBounds, clipExpandedBounds);
|
||||
rectsClose(initialDestinationBounds, clipDestBounds);
|
||||
rectsClose(initialDestinationBounds, clipExpandedBounds);
|
||||
canvas.restore();
|
||||
|
||||
// save/restore returned the values to their original values
|
||||
@@ -265,7 +257,7 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
const ui.Rect scaledExpandedBounds = ui.Rect.fromLTRB(5, 5.5, 10.5, 13);
|
||||
rectsClose(canvas.getLocalClipBounds(), scaledExpandedBounds);
|
||||
// Destination bounds are unaffected by transform
|
||||
rectsClose(canvas.getDestinationClipBounds(), clipDestBounds);
|
||||
rectsClose(canvas.getDestinationClipBounds(), clipExpandedBounds);
|
||||
canvas.restore();
|
||||
|
||||
// save/restore returned the values to their original values
|
||||
@@ -278,14 +270,13 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, const ui.Rect.fromLTRB(0, 0, 100, 100));
|
||||
const ui.Rect clipRawBounds = ui.Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
|
||||
const ui.Rect clipExpandedBounds = ui.Rect.fromLTRB(10, 11, 21, 26);
|
||||
final ui.Rect clipDestBounds = deviceClipRoundsOut ? clipExpandedBounds : clipRawBounds;
|
||||
canvas.clipRect(clipRawBounds);
|
||||
|
||||
// Save initial return values for testing restored values
|
||||
final ui.Rect initialLocalBounds = canvas.getLocalClipBounds();
|
||||
final ui.Rect initialDestinationBounds = canvas.getDestinationClipBounds();
|
||||
rectsClose(initialLocalBounds, clipExpandedBounds);
|
||||
rectsClose(initialDestinationBounds, clipDestBounds);
|
||||
rectsClose(initialDestinationBounds, clipExpandedBounds);
|
||||
|
||||
canvas.clipRect(const ui.Rect.fromLTRB(0, 0, 15, 15), clipOp: ui.ClipOp.difference);
|
||||
expect(canvas.getLocalClipBounds(), initialLocalBounds);
|
||||
|
||||
@@ -123,7 +123,7 @@ Future<void> testMain() async {
|
||||
canvas.drawImage(atlas, ui.Offset.zero, ui.Paint());
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
await matchGoldenFile('ui_atlas.png', region: region);
|
||||
}, skip: isHtml); // HTML renderer doesn't support drawAtlas
|
||||
});
|
||||
|
||||
test('drawAtlas', () async {
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
@@ -160,7 +160,7 @@ Future<void> testMain() async {
|
||||
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
await matchGoldenFile('ui_draw_atlas.png', region: region);
|
||||
}, skip: isHtml); // HTML renderer doesn't support drawAtlas
|
||||
});
|
||||
|
||||
test('drawAtlasRaw', () async {
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
@@ -209,5 +209,5 @@ Future<void> testMain() async {
|
||||
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
await matchGoldenFile('ui_draw_atlas_raw.png', region: region);
|
||||
}, skip: isHtml); // HTML renderer doesn't support drawAtlas
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,14 +53,14 @@ Future<void> testMain() async {
|
||||
ui.Paint()..imageFilter = ui.ImageFilter.dilate(radiusX: 5.0, radiusY: 5.0),
|
||||
);
|
||||
await matchGoldenFile('ui_filter_dilate_imagefilter.png', region: region);
|
||||
}, skip: isHtml); // HTML renderer does not support the dilate filter
|
||||
});
|
||||
|
||||
test('erode filter', () async {
|
||||
await drawTestImageWithPaint(
|
||||
ui.Paint()..imageFilter = ui.ImageFilter.erode(radiusX: 5.0, radiusY: 5.0),
|
||||
);
|
||||
await matchGoldenFile('ui_filter_erode_imagefilter.png', region: region);
|
||||
}, skip: isHtml); // HTML renderer does not support the erode filter
|
||||
});
|
||||
|
||||
test('matrix filter', () async {
|
||||
await drawTestImageWithPaint(
|
||||
@@ -94,7 +94,7 @@ Future<void> testMain() async {
|
||||
);
|
||||
await drawTestImageWithPaint(ui.Paint()..imageFilter = filter);
|
||||
await matchGoldenFile('ui_filter_composed_imagefilters.png', region: region);
|
||||
}, skip: isHtml); // Only Skwasm and CanvasKit implement composable filters right now.
|
||||
});
|
||||
|
||||
test('compose with colorfilter', () async {
|
||||
final ui.ImageFilter filter = ui.ImageFilter.compose(
|
||||
@@ -103,7 +103,7 @@ Future<void> testMain() async {
|
||||
);
|
||||
await drawTestImageWithPaint(ui.Paint()..imageFilter = filter);
|
||||
await matchGoldenFile('ui_filter_composed_colorfilter.png', region: region);
|
||||
}, skip: isHtml); // Only Skwasm and CanvasKit implements composable filters right now.
|
||||
});
|
||||
|
||||
test('color filter as image filter', () async {
|
||||
const ui.ColorFilter colorFilter = ui.ColorFilter.mode(
|
||||
@@ -136,14 +136,14 @@ Future<void> testMain() async {
|
||||
await drawTestImageWithPaint(ui.Paint()..colorFilter = colorFilter);
|
||||
await matchGoldenFile('ui_filter_linear_to_srgb_colorfilter.png', region: region);
|
||||
expect(colorFilter.toString(), 'ColorFilter.linearToSrgbGamma()');
|
||||
}, skip: isHtml); // HTML renderer hasn't implemented this.
|
||||
});
|
||||
|
||||
test('srgbToLinearGamma color filter', () async {
|
||||
const ui.ColorFilter colorFilter = ui.ColorFilter.srgbToLinearGamma();
|
||||
await drawTestImageWithPaint(ui.Paint()..colorFilter = colorFilter);
|
||||
await matchGoldenFile('ui_filter_srgb_to_linear_colorfilter.png', region: region);
|
||||
expect(colorFilter.toString(), 'ColorFilter.srgbToLinearGamma()');
|
||||
}, skip: isHtml); // HTML renderer hasn't implemented this.
|
||||
});
|
||||
|
||||
test('matrix color filter', () async {
|
||||
const ui.ColorFilter sepia = ui.ColorFilter.matrix(<double>[
|
||||
@@ -412,62 +412,31 @@ Future<void> testMain() async {
|
||||
return ui.Rect.fromLTWH(0, 0, offset * columns + pad, offset * rows + pad);
|
||||
}
|
||||
|
||||
test(
|
||||
'Rendering ops with ImageFilter blur with default tile mode',
|
||||
() async {
|
||||
final region = await renderingOpsWithTileMode(null);
|
||||
await matchGoldenFile(
|
||||
'ui_filter_blurred_rendering_with_default_tile_mode.png',
|
||||
region: region,
|
||||
);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
test('Rendering ops with ImageFilter blur with default tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(null);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_default_tile_mode.png', region: region);
|
||||
});
|
||||
|
||||
test(
|
||||
'Rendering ops with ImageFilter blur with clamp tile mode',
|
||||
() async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.clamp);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_clamp_tile_mode.png', region: region);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
test('Rendering ops with ImageFilter blur with clamp tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.clamp);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_clamp_tile_mode.png', region: region);
|
||||
});
|
||||
|
||||
test(
|
||||
'Rendering ops with ImageFilter blur with decal tile mode',
|
||||
() async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.decal);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_decal_tile_mode.png', region: region);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
test('Rendering ops with ImageFilter blur with decal tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.decal);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_decal_tile_mode.png', region: region);
|
||||
});
|
||||
|
||||
test(
|
||||
'Rendering ops with ImageFilter blur with mirror tile mode',
|
||||
() async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.mirror);
|
||||
await matchGoldenFile(
|
||||
'ui_filter_blurred_rendering_with_mirror_tile_mode.png',
|
||||
region: region,
|
||||
);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
test('Rendering ops with ImageFilter blur with mirror tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.mirror);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_mirror_tile_mode.png', region: region);
|
||||
});
|
||||
|
||||
test(
|
||||
'Rendering ops with ImageFilter blur with repeated tile mode',
|
||||
() async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.repeated);
|
||||
await matchGoldenFile(
|
||||
'ui_filter_blurred_rendering_with_repeated_tile_mode.png',
|
||||
region: region,
|
||||
);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
test('Rendering ops with ImageFilter blur with repeated tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.repeated);
|
||||
await matchGoldenFile(
|
||||
'ui_filter_blurred_rendering_with_repeated_tile_mode.png',
|
||||
region: region,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import 'package:ui/src/engine.dart';
|
||||
|
||||
import '../common/fake_asset_manager.dart';
|
||||
import '../common/test_initialization.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -35,10 +34,7 @@ Future<void> testMain() async {
|
||||
() async {
|
||||
final FlutterFontCollection collection = renderer.fontCollection;
|
||||
final ByteBuffer ahemData = await httpFetchByteBuffer('/assets/fonts/ahem.ttf');
|
||||
expect(
|
||||
await collection.loadFontFromList(ahemData.asUint8List()),
|
||||
!isHtml, // HtmlFontCollection requires family name
|
||||
);
|
||||
expect(await collection.loadFontFromList(ahemData.asUint8List()), isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -100,13 +96,7 @@ Future<void> testMain() async {
|
||||
);
|
||||
expect(result.loadedFonts, <String>[robotoVariableFontUrl, robotoTestFontUrl]);
|
||||
expect(result.fontFailures, hasLength(1));
|
||||
if (isHtml) {
|
||||
// The HTML renderer doesn't have a way to differentiate 404's from other
|
||||
// download errors.
|
||||
expect(result.fontFailures[invalidFontUrl], isA<FontDownloadError>());
|
||||
} else {
|
||||
expect(result.fontFailures[invalidFontUrl], isA<FontNotFoundError>());
|
||||
}
|
||||
expect(result.fontFailures[invalidFontUrl], isA<FontNotFoundError>());
|
||||
});
|
||||
|
||||
test('Loading asset fonts reports when a font has invalid data', () async {
|
||||
@@ -141,13 +131,7 @@ Future<void> testMain() async {
|
||||
);
|
||||
expect(result.loadedFonts, <String>[robotoVariableFontUrl, robotoTestFontUrl]);
|
||||
expect(result.fontFailures, hasLength(1));
|
||||
if (isHtml) {
|
||||
// The HTML renderer doesn't have a way to differentiate invalid data
|
||||
// from other download errors.
|
||||
expect(result.fontFailures[invalidFontUrl], isA<FontDownloadError>());
|
||||
} else {
|
||||
expect(result.fontFailures[invalidFontUrl], isA<FontInvalidDataError>());
|
||||
}
|
||||
expect(result.fontFailures[invalidFontUrl], isA<FontInvalidDataError>());
|
||||
});
|
||||
|
||||
test('Font manifest with numeric and string descriptor values parses correctly', () async {
|
||||
|
||||
@@ -96,5 +96,5 @@ Future<void> testMain() async {
|
||||
// Make sure we can reuse the shader object with a new uniform value and the same Paint object.
|
||||
shader.setFloat(0, 25.0);
|
||||
await drawCircleReusePaint('fragment_shader_voronoi_tile25px_reuse_paint.png');
|
||||
}, skip: isHtml); // Fragment shaders are not supported by the HTML renderer.
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine/browser_detection.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
@@ -104,5 +103,5 @@ Future<void> testMain() async {
|
||||
|
||||
await matchGoldenFile('sweep_gradient_paint.png', region: region);
|
||||
});
|
||||
}, skip: isFirefox && isHtml); // https://github.com/flutter/flutter/issues/86623
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine/canvaskit/image.dart';
|
||||
import 'package:ui/src/engine/dom.dart';
|
||||
import 'package:ui/src/engine/html/image.dart';
|
||||
import 'package:ui/src/engine/html_image_element_codec.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
@@ -63,12 +62,12 @@ Future<void> testMain() async {
|
||||
expect(image.height, height);
|
||||
});
|
||||
test('loads sample image', () async {
|
||||
final HtmlImageElementCodec codec = createImageElementCodec('sample_image1.png');
|
||||
final HtmlImageElementCodec codec = CkImageElementCodec('sample_image1.png');
|
||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
||||
|
||||
expect(codec.imgElement, isNotNull);
|
||||
expect(codec.imgElement!.src, contains('sample_image1.png'));
|
||||
expect(codec.imgElement!.crossOrigin, isHtml ? isNull : 'anonymous');
|
||||
expect(codec.imgElement!.crossOrigin, 'anonymous');
|
||||
expect(codec.imgElement!.decoding, 'async');
|
||||
|
||||
expect(frameInfo.image, isNotNull);
|
||||
@@ -76,7 +75,7 @@ Future<void> testMain() async {
|
||||
expect(frameInfo.image.toString(), '[100×100]');
|
||||
});
|
||||
test('dispose image image', () async {
|
||||
final HtmlImageElementCodec codec = createImageElementCodec('sample_image1.png');
|
||||
final HtmlImageElementCodec codec = CkImageElementCodec('sample_image1.png');
|
||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
||||
expect(frameInfo.image, isNotNull);
|
||||
expect(frameInfo.image.debugDisposed, isFalse);
|
||||
@@ -85,7 +84,7 @@ Future<void> testMain() async {
|
||||
});
|
||||
test('provides image loading progress', () async {
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
final HtmlImageElementCodec codec = createImageElementCodec(
|
||||
final HtmlImageElementCodec codec = CkImageElementCodec(
|
||||
'sample_image1.png',
|
||||
chunkCallback: (int loaded, int total) {
|
||||
buffer.write('$loaded/$total,');
|
||||
@@ -98,7 +97,7 @@ Future<void> testMain() async {
|
||||
/// Regression test for Firefox
|
||||
/// https://github.com/flutter/flutter/issues/66412
|
||||
test('Returns nonzero natural width/height', () async {
|
||||
final HtmlImageElementCodec codec = createImageElementCodec(
|
||||
final HtmlImageElementCodec codec = CkImageElementCodec(
|
||||
'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9I'
|
||||
'jAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dG'
|
||||
'l0bGU+QWJzdHJhY3QgaWNvbjwvdGl0bGU+PHBhdGggZD0iTTEyIDBjOS42MDEgMCAx'
|
||||
@@ -124,7 +123,7 @@ Future<void> testMain() async {
|
||||
|
||||
expect(codec.imgElement, isNotNull);
|
||||
expect(codec.imgElement!.src, contains('sample_image1.png'));
|
||||
expect(codec.imgElement!.crossOrigin, isHtml ? isNull : 'anonymous');
|
||||
expect(codec.imgElement!.crossOrigin, 'anonymous');
|
||||
expect(codec.imgElement!.decoding, 'async');
|
||||
|
||||
expect(frameInfo.image, isNotNull);
|
||||
@@ -146,12 +145,3 @@ Future<void> testMain() async {
|
||||
});
|
||||
}, skip: isSkwasm);
|
||||
}
|
||||
|
||||
HtmlImageElementCodec createImageElementCodec(
|
||||
String src, {
|
||||
ui_web.ImageCodecChunkCallback? chunkCallback,
|
||||
}) {
|
||||
return isHtml
|
||||
? HtmlRendererImageCodec(src, chunkCallback: chunkCallback)
|
||||
: CkImageElementCodec(src, chunkCallback: chunkCallback);
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ Future<void> testMain() async {
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
|
||||
await matchGoldenFile('${name}_fragment_shader_sampler.png', region: drawRegion);
|
||||
}, skip: isHtml); // HTML doesn't support fragment shaders
|
||||
});
|
||||
|
||||
test('drawVertices with image shader', () async {
|
||||
final ui.Image image = await generateImage();
|
||||
@@ -275,7 +275,7 @@ Future<void> testMain() async {
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
|
||||
await matchGoldenFile('${name}_drawVertices_imageShader.png', region: drawRegion);
|
||||
}, skip: isHtml); // https://github.com/flutter/flutter/issues/127454;
|
||||
});
|
||||
|
||||
test('toByteData_rgba', () async {
|
||||
final ui.Image image = await generateImage();
|
||||
@@ -294,7 +294,7 @@ Future<void> testMain() async {
|
||||
final ByteData? pngData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
expect(pngData, isNotNull);
|
||||
expect(pngData!.lengthInBytes, isNonZero);
|
||||
}, skip: isHtml); // https://github.com/flutter/flutter/issues/126611
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -347,32 +347,29 @@ Future<void> testMain() async {
|
||||
return completer.future;
|
||||
});
|
||||
|
||||
// https://github.com/flutter/flutter/issues/126603
|
||||
if (!isHtml) {
|
||||
emitImageTests('decodeImageFromPixels_scaled', () {
|
||||
final Uint8List pixels = generatePixelData(50, 50, (double x, double y) {
|
||||
final double r = sqrt(x * x + y * y);
|
||||
final double theta = atan2(x, y);
|
||||
return ui.Color.fromRGBO(
|
||||
(255 * (sin(r * 10.0) + 1.0) / 2.0).round(),
|
||||
(255 * (sin(theta * 10.0) + 1.0) / 2.0).round(),
|
||||
0,
|
||||
1,
|
||||
);
|
||||
});
|
||||
final Completer<ui.Image> completer = Completer<ui.Image>();
|
||||
ui.decodeImageFromPixels(
|
||||
pixels,
|
||||
50,
|
||||
50,
|
||||
ui.PixelFormat.rgba8888,
|
||||
completer.complete,
|
||||
targetWidth: 150,
|
||||
targetHeight: 150,
|
||||
emitImageTests('decodeImageFromPixels_scaled', () {
|
||||
final Uint8List pixels = generatePixelData(50, 50, (double x, double y) {
|
||||
final double r = sqrt(x * x + y * y);
|
||||
final double theta = atan2(x, y);
|
||||
return ui.Color.fromRGBO(
|
||||
(255 * (sin(r * 10.0) + 1.0) / 2.0).round(),
|
||||
(255 * (sin(theta * 10.0) + 1.0) / 2.0).round(),
|
||||
0,
|
||||
1,
|
||||
);
|
||||
return completer.future;
|
||||
});
|
||||
}
|
||||
final Completer<ui.Image> completer = Completer<ui.Image>();
|
||||
ui.decodeImageFromPixels(
|
||||
pixels,
|
||||
50,
|
||||
50,
|
||||
ui.PixelFormat.rgba8888,
|
||||
completer.complete,
|
||||
targetWidth: 150,
|
||||
targetHeight: 150,
|
||||
);
|
||||
return completer.future;
|
||||
});
|
||||
|
||||
emitImageTests('codec_uri', () async {
|
||||
final ui.Codec codec = await renderer.instantiateImageCodecFromUrl(
|
||||
@@ -444,7 +441,7 @@ Future<void> testMain() async {
|
||||
|
||||
// This API doesn't work in headless Firefox due to requiring WebGL
|
||||
// See https://github.com/flutter/flutter/issues/109265
|
||||
if (!isFirefox && !isHtml) {
|
||||
if (!isFirefox) {
|
||||
emitImageTests('svg_image_bitmap_texture_source', () async {
|
||||
final DomBlob svgBlob = createDomBlob(
|
||||
<String>[
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:ui/ui.dart' as ui;
|
||||
import 'package:ui/ui_web/src/ui_web/testing.dart';
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -26,7 +25,7 @@ Future<void> testMain() async {
|
||||
expect(paragraph.getLineMetricsAt(0), isNull);
|
||||
expect(paragraph.numberOfLines, 0);
|
||||
expect(paragraph.getLineNumberAt(0), isNull);
|
||||
}, skip: isHtml); // https://github.com/flutter/flutter/issues/144412
|
||||
});
|
||||
|
||||
test('Basic line related metrics', () {
|
||||
const double fontSize = 10;
|
||||
@@ -136,7 +135,7 @@ Future<void> testMain() async {
|
||||
case final List<ui.LineMetrics> metrics:
|
||||
expect(metrics, hasLength(1));
|
||||
}
|
||||
}, skip: isHtml); // The rounding hack doesn't apply to the html renderer
|
||||
});
|
||||
|
||||
test('overrides with flutter test font when debugEmulateFlutterTesterEnvironment is enabled', () {
|
||||
final ui.ParagraphBuilder builder = ui.ParagraphBuilder(ui.ParagraphStyle());
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -102,26 +101,18 @@ Future<void> testMain() async {
|
||||
expect(paragraph.height, lessThan(2 * fontSize));
|
||||
});
|
||||
|
||||
test(
|
||||
'kTextHeightNone StrutStyle',
|
||||
() {
|
||||
const double fontSize = 10;
|
||||
final ParagraphBuilder builder = ParagraphBuilder(
|
||||
ParagraphStyle(
|
||||
fontSize: 100,
|
||||
fontFamily: 'FlutterTest',
|
||||
strutStyle: StrutStyle(
|
||||
forceStrutHeight: true,
|
||||
height: kTextHeightNone,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
),
|
||||
);
|
||||
builder.addText('A');
|
||||
final Paragraph paragraph = builder.build()..layout(const ParagraphConstraints(width: 1000));
|
||||
// The height should be much smaller than fontSize * 10.
|
||||
expect(paragraph.height, lessThan(2 * fontSize));
|
||||
},
|
||||
skip: isHtml, // The HTML renderer does not support struts.
|
||||
);
|
||||
test('kTextHeightNone StrutStyle', () {
|
||||
const double fontSize = 10;
|
||||
final ParagraphBuilder builder = ParagraphBuilder(
|
||||
ParagraphStyle(
|
||||
fontSize: 100,
|
||||
fontFamily: 'FlutterTest',
|
||||
strutStyle: StrutStyle(forceStrutHeight: true, height: kTextHeightNone, fontSize: fontSize),
|
||||
),
|
||||
);
|
||||
builder.addText('A');
|
||||
final Paragraph paragraph = builder.build()..layout(const ParagraphConstraints(width: 1000));
|
||||
// The height should be much smaller than fontSize * 10.
|
||||
expect(paragraph.height, lessThan(2 * fontSize));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -52,8 +51,7 @@ Future<void> testMain() async {
|
||||
// the bounds on this will be the same as union - but would draw a missing inside piece.
|
||||
final Path xor = Path.combine(PathOperation.xor, pathCircle1, pathCircle2);
|
||||
expect(xor.getBounds(), equals(c1UnionC2));
|
||||
// TODO(hterkelsen): Implement Path.combine in the HTML renderer, https://github.com/flutter/flutter/issues/44572
|
||||
}, skip: isHtml);
|
||||
});
|
||||
|
||||
test('path combine oval', () {
|
||||
final Rect c1 = Rect.fromCircle(center: const Offset(10.0, 10.0), radius: 10.0);
|
||||
@@ -83,8 +81,7 @@ Future<void> testMain() async {
|
||||
// the bounds on this will be the same as union - but would draw a missing inside piece.
|
||||
final Path xor = Path.combine(PathOperation.xor, pathCircle1, pathCircle2);
|
||||
expect(xor.getBounds(), equals(c1UnionC2));
|
||||
// TODO(hterkelsen): Implement Path.combine in the HTML renderer, https://github.com/flutter/flutter/issues/44572
|
||||
}, skip: isHtml);
|
||||
});
|
||||
|
||||
test('path clone', () {
|
||||
final Path p1 = Path()..lineTo(20.0, 20.0);
|
||||
@@ -199,8 +196,7 @@ Future<void> testMain() async {
|
||||
expect(multiContourMetric.iterator.current.length, equals(5.0));
|
||||
expect(multiContourMetric.iterator.moveNext(), isFalse);
|
||||
expect(() => multiContourMetric.iterator.current, throwsRangeError);
|
||||
// TODO(hterkelsen): forceClosed in computeMetrics is ignored in the HTML renderer, https://github.com/flutter/flutter/issues/114446
|
||||
}, skip: isHtml);
|
||||
});
|
||||
|
||||
test('PathMetrics can remember lengths and isClosed', () {
|
||||
final Path path =
|
||||
@@ -219,8 +215,7 @@ Future<void> testMain() async {
|
||||
expect(metrics[1].isClosed, false);
|
||||
expect(metrics[1].getTangentForOffset(4.0)!.vector, const Offset(1.0, 0.0));
|
||||
expect(metrics[1].extractPath(4.0, 6.0).computeMetrics().first.length, 2.0);
|
||||
// TODO(hterkelsen): isClosed always returns false in the HTML renderer, https://github.com/flutter/flutter/issues/114446
|
||||
}, skip: isHtml);
|
||||
});
|
||||
|
||||
test('PathMetrics on a mutated path', () {
|
||||
final Path path = Path()..lineTo(0, 10);
|
||||
@@ -252,8 +247,7 @@ Future<void> testMain() async {
|
||||
expect(newFirstMetric.isClosed, true);
|
||||
expect(newFirstMetric.getTangentForOffset(4.0)!.vector, const Offset(0.0, 1.0));
|
||||
expect(newFirstMetric.extractPath(4.0, 10.0).computeMetrics().first.length, 6.0);
|
||||
// TODO(hterkelsen): isClosed always returns false in the HTML renderer, https://github.com/flutter/flutter/issues/114446
|
||||
}, skip: isHtml);
|
||||
});
|
||||
|
||||
test('path relativeLineTo', () {
|
||||
final p = Path();
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:ui/ui.dart' as ui;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
const ui.Color black = ui.Color(0xFF000000);
|
||||
const ui.Color red = ui.Color(0xFFFF0000);
|
||||
@@ -57,7 +56,7 @@ Future<void> testMain() async {
|
||||
host1.remove();
|
||||
host2.remove();
|
||||
host3.remove();
|
||||
}, skip: isHtml); // HTML renderer doesn't support multi-view.
|
||||
});
|
||||
}
|
||||
|
||||
DomElement createHostElement(ui.Rect rect) {
|
||||
|
||||
@@ -13,7 +13,6 @@ import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../common/rendering.dart';
|
||||
import '../common/test_initialization.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -210,7 +209,7 @@ Future<void> testMain() async {
|
||||
|
||||
await renderScene(sceneBuilder.build());
|
||||
await matchGoldenFile('scene_builder_shader_mask.png', region: region);
|
||||
}, skip: isFirefox && isHtml); // https://github.com/flutter/flutter/issues/86623
|
||||
});
|
||||
|
||||
test('backdrop filter layer', () async {
|
||||
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();
|
||||
@@ -527,7 +526,7 @@ Future<void> testMain() async {
|
||||
await renderScene(sceneBuilder.build());
|
||||
|
||||
await matchGoldenFile('scene_builder_shader_mask_clipped_out.png', region: region);
|
||||
}, skip: isFirefox && isHtml); // https://github.com/flutter/flutter/issues/86623
|
||||
});
|
||||
|
||||
test('opacity layer with transformed children', () async {
|
||||
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();
|
||||
@@ -597,80 +596,55 @@ Future<void> testMain() async {
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'backdrop layer with default blur tile mode',
|
||||
() async {
|
||||
final scene = backdropBlurWithTileMode(null, 10, 50);
|
||||
await renderScene(scene);
|
||||
test('backdrop layer with default blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(null, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_default_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_default_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'backdrop layer with clamp blur tile mode',
|
||||
() async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.clamp, 10, 50);
|
||||
await renderScene(scene);
|
||||
test('backdrop layer with clamp blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.clamp, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_clamp_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_clamp_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'backdrop layer with mirror blur tile mode',
|
||||
() async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.mirror, 10, 50);
|
||||
await renderScene(scene);
|
||||
test('backdrop layer with mirror blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.mirror, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_mirror_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_mirror_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'backdrop layer with repeated blur tile mode',
|
||||
() async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.repeated, 10, 50);
|
||||
await renderScene(scene);
|
||||
test('backdrop layer with repeated blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.repeated, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_repeated_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_repeated_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'backdrop layer with decal blur tile mode',
|
||||
() async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.decal, 10, 50);
|
||||
await renderScene(scene);
|
||||
test('backdrop layer with decal blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.decal, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_decal_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml,
|
||||
);
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_decal_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10 * 50, 10 * 50),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,6 @@ FlutterView get implicitView => EnginePlatformDispatcher.instance.implicitView!;
|
||||
/// Returns [true] if this test is running in the CanvasKit renderer.
|
||||
bool get isCanvasKit => renderer is CanvasKitRenderer;
|
||||
|
||||
/// Returns [true] if this test is running in the HTML renderer.
|
||||
bool get isHtml => renderer is HtmlRenderer;
|
||||
|
||||
bool get isSkwasm => renderer is SkwasmRenderer;
|
||||
|
||||
bool get isMultiThreaded => isSkwasm && (renderer as SkwasmRenderer).isMultiThreaded;
|
||||
|
||||
@@ -61,7 +61,7 @@ void testMain() {
|
||||
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
await matchGoldenFile('ui_vertices_antialiased.png', region: region);
|
||||
}, skip: isHtml); // https://github.com/flutter/flutter/issues/127454
|
||||
});
|
||||
}
|
||||
|
||||
ui.Vertices _testVertices() {
|
||||
|
||||
Reference in New Issue
Block a user