The other day I decided I wanted to add a feature in one of my projects to share a graph and I was looking for ways to convert the graph into an image. In the end the approach that I landed on was using the RepaintBoundary widget and I'm going to explain how to use it in this post.
The widget
First of all let's just start with a widget, a green Container with some text inside. Here's what the code for it looks like:
Container(
decoration: BoxDecoration(
color: Colors.green,
),
height: 200,
width: 400,
child: Center(
child: Text(
"Hello world!",
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
)
)
)
And here's what the above widget looks like visually(this was generated using DartPad):
Wrap it with RepaintBoundary
To convert this widget to an image file(or capture/screenshot it) you need to wrap the widget in a RepaintBoundary
widget:
RepaintBoundary(
child: Container(
decoration: BoxDecoration(
color: Colors.green,
),
height: 200,
width: 400,
child: Center(
child: Text(
"Hello world!",
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
),
)
Then you need to create a GlobalKey
and pass that to the RepaintBoundary
widget:
final key = GlobalKey();
...
RepaintBoundary(
key: key,
child: Container(
decoration: BoxDecoration(
color: Colors.green,
),
height: 200,
width: 400,
child: Center(
child: Text(
"Hello world!",
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
),
)
Create the image
Now the last step is to use the key above kind of like a controller and create an image representation of the widget wrapped inside RepaintBoundary
. I probably should also add that the widget you wrap doesn't have to be a Container
widget. Okay, onto the code for getting the bytes for an image. You could trigger this code with a button or something along those lines:
final boundary = key.currentContext?.findRenderObject() as RenderRepaintBoundary?;
final image = await boundary?.toImage();
final byteData = await image?.toByteData(format: ImageByteFormat.png);
final imageBytes = byteData?.buffer.asUint8List();
Then the next step is to write those bytes to a file somewhere(you will need the path_provider
package to use getApplicationDocumentsDirectory
):
if (imageBytes != null) {
final directory = await getApplicationDocumentsDirectory();
final imagePath = await File('${directory.path}/container_image.png').create();
await imagePath.writeAsBytes(imageBytes);
}
And here is the full code for this step:
final boundary = key.currentContext?.findRenderObject() as RenderRepaintBoundary?;
final image = await boundary?.toImage();
final byteData = await image?.toByteData(format: ImageByteFormat.png);
final imageBytes = byteData?.buffer.asUint8List();
if (imageBytes != null) {
final directory = await getApplicationDocumentsDirectory();
final imagePath = await File('${directory.path}/container_image.png').create();
await imagePath.writeAsBytes(imageBytes);
}
Once you execute this code an image representation of the widget will be created in the applications document directory of your app. Here's the output I got after I executed the code on an iOS simulator:
By the way you can view the contents of a simulator's document directory using the following command on MacOS:
open `xcrun simctl get_app_container booted [Bundle ID] data`
Some caveats
I'll close this post with a couple of caveats with the approach above:
- The resulting image might end up pixelated in some cases. You can rectify this by passing the
pixelRatio
parameter when usingawait boundary?.toImage()
. You might want to combine this withMediaQuery.of(context).devicePixelRatio
for the best effect. I learnt about this from the README of an excellent package called screenshot. (The reason I wrote my own implementation is because I already use a lot of packages in the project I was working on and didn't want to add any more...) - The generated image will be in PNG format. I'm not sure if you can generate a JPEG image or how you would do it. I might end up investigating this in the future.
Top comments (0)