| // 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'; |
| |
| /// 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; |
| |
| 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 { |
| setUpAll(() async { |
| debugShowClipLayers = true; |
| await webOnlyInitializePlatform(); |
| }); |
| |
| setUp(() async { |
| SurfaceSceneBuilder.debugForgetFrameScene(); |
| for (final DomNode scene in |
| flutterViewEmbedder.sceneHostElement!.querySelectorAll('flt-scene').cast<DomNode>()) { |
| scene.remove(); |
| } |
| initWebGl(); |
| await renderer.fontCollection.debugDownloadTestFonts(); |
| renderer.fontCollection.registerDownloadedFonts(); |
| }); |
| |
| /// 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(); |
| |
| flutterViewEmbedder.sceneHostElement!.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(); |
| |
| flutterViewEmbedder.sceneHostElement!.append(builder.build().webOnlyRootElement!); |
| } |