1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:typed_data'; 6import 'dart:ui'; 7import 'dart:io'; 8import 'package:image/image.dart' as dart_image; 9 10import 'package:path/path.dart' as path; 11import 'package:test/test.dart'; 12 13typedef CanvasCallback = void Function(Canvas canvas); 14 15void testCanvas(CanvasCallback callback) { 16 try { 17 callback(Canvas(PictureRecorder(), const Rect.fromLTRB(0.0, 0.0, 0.0, 0.0))); 18 } catch (error) { } // ignore: empty_catches 19} 20 21void testNoCrashes() { 22 test('canvas APIs should not crash', () async { 23 final Paint paint = Paint(); 24 const Rect rect = Rect.fromLTRB(double.nan, double.nan, double.nan, double.nan); 25 final RRect rrect = RRect.fromRectAndCorners(rect); 26 const Offset offset = Offset(double.nan, double.nan); 27 final Path path = Path(); 28 const Color color = Color(0); 29 final Paragraph paragraph = ParagraphBuilder(ParagraphStyle()).build(); 30 31 final PictureRecorder recorder = PictureRecorder(); 32 final Canvas recorderCanvas = Canvas(recorder); 33 recorderCanvas.scale(1.0, 1.0); 34 final Picture picture = recorder.endRecording(); 35 final Image image = await picture.toImage(1, 1); 36 37 try { Canvas(null, null); } catch (error) { } // ignore: empty_catches 38 try { Canvas(null, rect); } catch (error) { } // ignore: empty_catches 39 try { Canvas(PictureRecorder(), null); } catch (error) { } // ignore: empty_catches 40 try { Canvas(PictureRecorder(), rect); } catch (error) { } // ignore: empty_catches 41 42 try { 43 PictureRecorder() 44 ..endRecording() 45 ..endRecording() 46 ..endRecording(); 47 } catch (error) { } // ignore: empty_catches 48 49 testCanvas((Canvas canvas) => canvas.clipPath(path)); 50 testCanvas((Canvas canvas) => canvas.clipRect(rect)); 51 testCanvas((Canvas canvas) => canvas.clipRRect(rrect)); 52 testCanvas((Canvas canvas) => canvas.drawArc(rect, 0.0, 0.0, false, paint)); 53 testCanvas((Canvas canvas) => canvas.drawAtlas(image, <RSTransform>[], <Rect>[], <Color>[], BlendMode.src, rect, paint)); 54 testCanvas((Canvas canvas) => canvas.drawCircle(offset, double.nan, paint)); 55 testCanvas((Canvas canvas) => canvas.drawColor(color, BlendMode.src)); 56 testCanvas((Canvas canvas) => canvas.drawDRRect(rrect, rrect, paint)); 57 testCanvas((Canvas canvas) => canvas.drawImage(image, offset, paint)); 58 testCanvas((Canvas canvas) => canvas.drawImageNine(image, rect, rect, paint)); 59 testCanvas((Canvas canvas) => canvas.drawImageRect(image, rect, rect, paint)); 60 testCanvas((Canvas canvas) => canvas.drawLine(offset, offset, paint)); 61 testCanvas((Canvas canvas) => canvas.drawOval(rect, paint)); 62 testCanvas((Canvas canvas) => canvas.drawPaint(paint)); 63 testCanvas((Canvas canvas) => canvas.drawParagraph(paragraph, offset)); 64 testCanvas((Canvas canvas) => canvas.drawPath(path, paint)); 65 testCanvas((Canvas canvas) => canvas.drawPicture(picture)); 66 testCanvas((Canvas canvas) => canvas.drawPoints(PointMode.points, <Offset>[], paint)); 67 testCanvas((Canvas canvas) => canvas.drawRawAtlas(image, Float32List(0), Float32List(0), Int32List(0), BlendMode.src, rect, paint)); 68 testCanvas((Canvas canvas) => canvas.drawRawPoints(PointMode.points, Float32List(0), paint)); 69 testCanvas((Canvas canvas) => canvas.drawRect(rect, paint)); 70 testCanvas((Canvas canvas) => canvas.drawRRect(rrect, paint)); 71 testCanvas((Canvas canvas) => canvas.drawShadow(path, color, double.nan, null)); 72 testCanvas((Canvas canvas) => canvas.drawShadow(path, color, double.nan, false)); 73 testCanvas((Canvas canvas) => canvas.drawShadow(path, color, double.nan, true)); 74 testCanvas((Canvas canvas) => canvas.drawVertices(Vertices(VertexMode.triangles, <Offset>[]), null, paint)); 75 testCanvas((Canvas canvas) => canvas.getSaveCount()); 76 testCanvas((Canvas canvas) => canvas.restore()); 77 testCanvas((Canvas canvas) => canvas.rotate(double.nan)); 78 testCanvas((Canvas canvas) => canvas.save()); 79 testCanvas((Canvas canvas) => canvas.saveLayer(rect, paint)); 80 testCanvas((Canvas canvas) => canvas.saveLayer(null, null)); 81 testCanvas((Canvas canvas) => canvas.scale(double.nan, double.nan)); 82 testCanvas((Canvas canvas) => canvas.skew(double.nan, double.nan)); 83 testCanvas((Canvas canvas) => canvas.transform(null)); 84 testCanvas((Canvas canvas) => canvas.translate(double.nan, double.nan)); 85 }); 86} 87 88/// @returns true When the images are resonably similar. 89/// @todo Make the search actually fuzzy to a certain degree. 90Future<bool> fuzzyCompareImages(Image golden, Image img) async { 91 if (golden.width != img.width || golden.height != img.height) { 92 return false; 93 } 94 int getPixel(ByteData data, int x, int y) => data.getUint32((x + y * golden.width) * 4); 95 final ByteData goldenData = await golden.toByteData(); 96 final ByteData imgData = await img.toByteData(); 97 for (int y = 0; y < golden.height; y++) { 98 for (int x = 0; x < golden.width; x++) { 99 if (getPixel(goldenData, x, y) != getPixel(imgData, x, y)) { 100 return false; 101 } 102 } 103 } 104 return true; 105} 106 107/// @returns true When the images are resonably similar. 108Future<bool> fuzzyGoldenImageCompare( 109 Image image, String goldenImageName) async { 110 final String imagesPath = path.join('flutter', 'testing', 'resources'); 111 final File file = File(path.join(imagesPath, goldenImageName)); 112 final Uint8List goldenData = await file.readAsBytes(); 113 114 final Codec codec = await instantiateImageCodec(goldenData); 115 final FrameInfo frame = await codec.getNextFrame(); 116 expect(frame.image.height, equals(image.width)); 117 expect(frame.image.width, equals(image.height)); 118 119 final bool areEqual = await fuzzyCompareImages(frame.image, image); 120 if (!areEqual) { 121 final ByteData pngData = await image.toByteData(); 122 final ByteBuffer buffer = pngData.buffer; 123 final dart_image.Image png = dart_image.Image.fromBytes( 124 image.width, image.height, buffer.asUint8List()); 125 final String outPath = path.join(imagesPath, 'found_' + goldenImageName); 126 File(outPath)..writeAsBytesSync(dart_image.encodePng(png)); 127 print('wrote: ' + outPath); 128 } 129 return areEqual; 130} 131 132void main() { 133 testNoCrashes(); 134 135 test('Simple .toImage', () async { 136 final PictureRecorder recorder = PictureRecorder(); 137 final Canvas canvas = Canvas(recorder); 138 final Path circlePath = Path() 139 ..addOval( 140 Rect.fromCircle(center: const Offset(40.0, 40.0), radius: 20.0)); 141 final Paint paint = Paint() 142 ..isAntiAlias = false 143 ..style = PaintingStyle.fill; 144 canvas.drawPath(circlePath, paint); 145 final Picture picture = recorder.endRecording(); 146 final Image image = await picture.toImage(100, 100); 147 expect(image.width, equals(100)); 148 expect(image.height, equals(100)); 149 150 final bool areEqual = 151 await fuzzyGoldenImageCompare(image, 'canvas_test_toImage.png'); 152 expect(areEqual, true); 153 }); 154} 155