4

Capturing a Flutter widget as an image using RepaintBoundary

 2 years ago
source link: https://dev.to/jordanm_h/capturing-a-flutter-widget-as-an-image-using-repaintboundary-3k50
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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,
      ),
    )
  )
)
Enter fullscreen modeExit fullscreen mode

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,
        ),
      ),
    ),
  ),
)
Enter fullscreen modeExit fullscreen mode

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,
        ),
      ),
    ),
  ),
)
Enter fullscreen modeExit fullscreen mode

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();
Enter fullscreen modeExit fullscreen mode

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);
}
Enter fullscreen modeExit fullscreen mode

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);
}
Enter fullscreen modeExit fullscreen mode

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`
Enter fullscreen modeExit fullscreen mode

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 using await boundary?.toImage(). You might want to combine this with MediaQuery.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.

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK