Saving picture of Canvas with PictureRecorder results in empty image. The first is that you shouldn’t be passing the canvas & points from Signature to SignatureState; that’s an antipattern in flutter.
This code here works. I’ve done a few little things that were unnecessary to the answer to clean it up as well, sorry about that =D.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | import 'dart:ui' as ui; import 'package:flutter/material.dart'; ///This class is use just to check if the Image returned by ///the PictureRecorder of the first Canvas is not empty. class CanvasImageDraw extends CustomPainter { ui.Image image; CanvasImageDraw(this.image); @override void paint(ui.Canvas canvas, ui.Size size) { // simple aspect fit for the image var hr = size.height / image.height; var wr = size.width / image.width; double ratio; double translateX; double translateY; if (hr < wr) { ratio = hr; translateX = (size.width - (ratio * image.width)) / 2; translateY = 0.0; } else { ratio = wr; translateX = 0.0; translateY = (size.height - (ratio * image.height)) / 2; } canvas.translate(translateX, translateY); canvas.scale(ratio, ratio); canvas.drawImage(image, new Offset(0.0, 0.0), new Paint()); } @override bool shouldRepaint(CanvasImageDraw oldDelegate) { return image != oldDelegate.image; } } ///Class used to display the second canvas class SecondView extends StatelessWidget { ui.Image image; var pictureRecorder = new ui.PictureRecorder(); SecondView({this.image}); @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Image Debug'), ), body: new Column( children: <Widget>[ new Text('Top'), CustomPaint( painter: new CanvasImageDraw(image), size: new Size(200.0, 200.0), ), new Text('Bottom'), ], )); } } ///This is the CustomPainter of the first Canvas which is used ///to draw to display the users draw/sign. class SignaturePainter extends CustomPainter { final List<Offset> points; final int revision; SignaturePainter(this.points, [this.revision = 0]); void paint(canvas, Size size) { if (points.length < 2) return; Paint paint = new Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5.0; for (int i = 0; i < points.length - 1; i++) { if (points[i] != null && points[i + 1] != null) canvas.drawLine(points[i], points[i + 1], paint); } } // simplified this, but if you ever modify points instead of changing length you'll // have to revise. bool shouldRepaint(SignaturePainter other) => other.revision != revision; } ///Classes used to get the user draw/sign, send it to the first canvas, ///then display it. ///'points' list of position returned by the GestureDetector class Signature extends StatefulWidget { Signature({Key key}): super(key: key); @override State<StatefulWidget> createState()=> new SignatureState(); } class SignatureState extends State<Signature> { List<Offset> _points = <Offset>[]; int _revision = 0; ui.Image get rendered { var pictureRecorder = new ui.PictureRecorder(); Canvas canvas = new Canvas(pictureRecorder); SignaturePainter painter = new SignaturePainter(_points); var size = context.size; // if you pass a smaller size here, it cuts off the lines painter.paint(canvas, size); // if you use a smaller size for toImage, it also cuts off the lines - so I've // done that in here as well, as this is the only place it's easy to get the width & height. return pictureRecorder.endRecording().toImage(size.width.floor(), size.height.floor()); } void _addToPoints(Offset position) { _revision++; _points.add(position); } Widget build(BuildContext context) { return new Stack( children: [ GestureDetector( onPanStart: (DragStartDetails details) { RenderBox referenceBox = context.findRenderObject(); Offset localPosition = referenceBox.globalToLocal(details.globalPosition); setState(() { _addToPoints(localPosition); }); }, onPanUpdate: (DragUpdateDetails details) { RenderBox referenceBox = context.findRenderObject(); Offset localPosition = referenceBox.globalToLocal(details.globalPosition); setState(() { _addToPoints(localPosition); }); }, onPanEnd: (DragEndDetails details) => setState(() => _addToPoints(null)), ), CustomPaint(painter: new SignaturePainter(_points, _revision)), ], ); } } ///The main class which display the first Canvas, Drawing/Signing area /// ///Floating action button used to stop the PictureRecorder's recording, ///then send the Picture to the next view/Canvas which should display it class DemoApp extends StatelessWidget { GlobalKey<SignatureState> signatureKey = new GlobalKey(); Widget build(BuildContext context) => new Scaffold( body: new Signature(key: signatureKey), floatingActionButton: new FloatingActionButton( child: new Icon(Icons.save), onPressed: () async { var image = signatureKey.currentState.rendered; Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondView(image: image))); }, ), ); } void main() => runApp(new MaterialApp(home: new DemoApp())); |
If you like this question & answer and want to contribute, then write your question & answer and email to freewebmentor[@]gmail.com. Your question and answer will appear on FreeWebMentor.com and help other developers.