| // 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:web_engine_tester/golden_tester.dart'; |
| |
| import '../screenshot.dart'; |
| |
| void main() { |
| internalBootstrapBrowserTest(() => testMain); |
| } |
| |
| Future<void> testMain() async { |
| |
| setUp(() async { |
| debugEmulateFlutterTesterEnvironment = true; |
| }); |
| |
| setUpStableTestFonts(); |
| |
| 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(); |
| |
| 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. |
| 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 |
| // Scuba 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 |
| // Scuba 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' |
| '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' |
| '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(); |
| } |