Flutter: How would one save a Canvas/CustomPainter to an image file?
Solution 1
You can capture the output of a CustomPainter
with PictureRecorder
. Pass your PictureRecorder
instance to the constructor for your Canvas
. The Picture
returned by PictureRecorder.endRecording
can then be converted to an Image
with Picture.toImage
. Finally, extract the image bytes using Image.toByteData
.
Here's an example: https://github.com/rxlabz/flutter_canvas_to_image
Solution 2
Add the rendered method in your widget
ui.Image get rendered {
// [CustomPainter] has its own @canvas to pass our
// [ui.PictureRecorder] object must be passed to [Canvas]#contructor
// to capture the Image. This way we can pass @recorder to [Canvas]#contructor
// using @painter[SignaturePainter] we can call [SignaturePainter]#paint
// with the our newly created @canvas
ui.PictureRecorder recorder = ui.PictureRecorder();
Canvas canvas = Canvas(recorder);
SignaturePainter painter = SignaturePainter(points: _points);
var size = context.size;
painter.paint(canvas, size);
return recorder.endRecording()
.toImage(size.width.floor(), size.height.floor());
}
Then using state fetch the rendered image
var image = signatureKey.currentState.rendered
Now, you can produce png Image using toByteData(format: ui.ImageByteFormat.png)
and store using asInt8List()
var pngBytes = await image.toByteData(format: ui.ImageByteFormat.png)
File('your-path/filename.png')
.writeAsBytesSync(pngBytes.buffer.asInt8List())
For complete example, on how to export canvas as png check out this example https://github.com/vemarav/signature
Solution 3
The existing solutions worked for me, but the images I captured with PictureRecorder
were always blurry vs. what was rendering on-screen. I eventually realized I could use some elementary Canvas tricks to pull this off. Basically, after you create the PictureRecorder
's Canvas
, set its size to multiple times your desired scale (here I have it set to 4x). Then just canvas.scale
it. Boom - your generated images are no longer blurry vs. what appears on screens with modern resolutions!
You may want to crank the _overSampleScale
value higher for printed or images that may be blown up/expanded, or lower if you're using this a ton and want to improve image preview loading performance. Using it on-screen, you'll need to constrain your Image.memory
Widget with a Container
of the actual width and height, as with the other solutions. Ideally this number would be the ratio between Flutter's DPI in its fake "pixels" (i.e. what PictureRecorder
captures) and the actual DPI of the screen.
static const double _overSampleScale = 4;
Future<ui.Image> get renderedScoreImage async {
final recorder = ui.PictureRecorder();
Canvas canvas = Canvas(recorder);
final size = Size(widget.width * _overSampleScale, widget.height * _overSampleScale);
final painter = SignaturePainter(points: _points);
canvas.save();
canvas.scale(_overSampleScale);
painter.paint(canvas, size);
canvas.restore();
final data = recorder.endRecording()
.toImage(size.width.floor(), size.height.floor());
return data;
}
Related videos on Youtube
Jus10
Updated on December 16, 2020Comments
-
Jus10 over 3 years
I am trying to collect a signature from the user and save it to an image. I have made it far enough that I can draw on the screen, but now I'd like to click a button to save to an image and store in my database.
This is what I have so far:
import 'package:flutter/material.dart'; class SignaturePadPage extends StatefulWidget { SignaturePadPage({Key key}) : super(key: key); @override _SignaturePadPage createState() => new _SignaturePadPage(); } class _SignaturePadPage extends State<SignaturePadPage> { List<Offset> _points = <Offset>[]; @override Widget build(BuildContext context) { return Container( color: Colors.white, child: GestureDetector( onPanUpdate: (DragUpdateDetails details) { setState(() { RenderBox referenceBox = context.findRenderObject(); Offset localPosition = referenceBox.globalToLocal(details.globalPosition); _points = new List.from(_points)..add(localPosition); }); }, onPanEnd: (DragEndDetails details) => _points.add(null), child: new CustomPaint(painter: new SignaturePainter(_points)), ), ); } } class SignaturePainter extends CustomPainter { SignaturePainter(this.points); final List<Offset> points; void paint(Canvas canvas, Size size) { 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); } } bool shouldRepaint(SignaturePainter other) => other.points != points; }
Not sure where to go from there...
-
Jus10 almost 6 yearsIn my code I'm using
paint(Canvas canvas, Size size)
so there is no Canvas contructor. How would I incorporate that in my situation? -
jspcal almost 6 yearsYou can add a
Canvas
to your layout or have paint update both the recordingCanvas
and the one that was passed in (paint to both or usedrawPicture
to copy from one to another). -
speedyGonzales almost 6 yearsActually I am having a strange error with your provided solution. I got Format exception, any idea why ? Here is the code gist.github.com/speedyGonzales/51d8cd1d94ffe568dddb20759dc9b383
-
Kamlesh over 3 yearshow can we create jpg image? png image is transparent and mixed with background. Please share any suggestion.
-
ChildhoodAndy about 3 yearsIf we want to save as gif or continuous pictures, how to do that?
-
Omar Abdelazeem over 2 years@Jus10 Define another canvas and recorder inside paint function and define global recorder and then make global recorder equal to local inside paint function . void paint(Canvas canvas, Size size) { PictureRecorder recorder = PictureRecorder(); outerRecorder = recorder; tempCanvas = Canvas(recorder); canvas.drawImage(image, Offset(0.0, 0.0), Paint()); tempCanvas.drawImage(image, Offset(0.0, 0.0), Paint()); for (Offset offset in points) { canvas.drawCircle(offset, 10, painter); tempCanvas.drawCircle(offset, 10, painter); } }
-
Arslan Kaleem over 2 yearsI am using the picture recorder but its seems that it drops the image and canvas quality so much. Although I have set
filterQualtiy
toFilterQuality.high
but still image I get is pixelated and blur even the circle I draw is very pixelated as compared to CustomPaint canvas -
Hamza Muazzam about 2 yearshow we can get it's reverse ? like ui.Image to canvas?