Flutter: How would one save a Canvas/CustomPainter to an image file?
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
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;
}
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