Flutter save widget directly into an image
Asked Answered
A

3

6

I have a custom widget that I want to save into a png image. The issue I'm facing is that with the current implementation is that I'm required to show the widget on the screen. What I want is to save the widget directly into the image without showing it.

As a workaround I'm saving the image at the first possible moment when it renders on the screen then quickly dismiss it.

This is how I save the widget now :


class SomeWidget extends StatefulWidget {
  const SomeWidget({
    Key key,
  }) : super(key: key);

  @override
  _ShareCocktailMockState createState() => _ShareCocktailMockState();
}

class _SomeWidgetState extends State<SomeWidget>
    with AfterLayoutMixin<SomeWidget> {
  GlobalKey globalKey = GlobalKey();

  Future<void> _capturePng() async {
    RenderRepaintBoundary boundary =
        globalKey.currentContext.findRenderObject();
    try {
      if (boundary.debugNeedsPaint) {
        print("Waiting for boundary to be painted.");
        await Future.delayed(const Duration(milliseconds: 5));
        return _capturePng();
      }
    } catch (_) {}
    try {
      ui.Image image = await boundary.toImage();
      ByteData byteData =
          await image.toByteData(format: ui.ImageByteFormat.png);
      Uint8List pngBytes = byteData.buffer.asUint8List();

      // SHARING IMAGE TO SOCIAL MEDIA
      // CODE

      // widget is presented with a dialog so I just pop it
      Navigator.of(context).pop();
    } catch (_) {
      await Future.delayed(const Duration(milliseconds: 5));
      return _capturePng();
    }
  }

  @override
  void afterFirstLayout(BuildContext context) {
    _capturePng();
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      key: globalKey,
      child: SuperFancyWidget(),
    );
  }
}

afterFirstLayout is from https://pub.dev/packages/after_layout package

Apoloniaapolune answered 28/7, 2020 at 15:34 Comment(0)
H
5

The screenshot package (https://pub.dev/packages/screenshot) has been updated to now support capturing a widget that isn't displayed on screen.

So something like below should work:

ScreenshotController screenshotController = ScreenshotController();
  late File customFile;
await screenshotController
              .captureFromWidget(CustomWidget())
              .then((capturedImage) async {
            await widgetToImageFile(capturedImage);
  Future<void> widgetToImageFile(
    Uint8List capturedImage,
  ) async {
    Directory tempDir = await getTemporaryDirectory();
    String tempPath = tempDir.path;
    final ts = DateTime.now().millisecondsSinceEpoch.toString();
    String path = '$tempPath/$ts.png';
    customFile = await File(path).writeAsBytes(capturedImage);
  }

Now you should have a custom file containing a .png of your widget to do with as you please.

Hardnett answered 17/3, 2022 at 19:48 Comment(0)
M
3

Okay if you want to save your widget as png picture, you can use the screenshot package. Wrap the widget like this:

Screenshot(
    controller: screenshotController,
    child: Text("This text will be captured as image"),
)

Read Readme section for details in the package.

Maquette answered 28/7, 2020 at 15:40 Comment(1)
The package you mentioned does exactly what I managed to do in my implementation. I was looking for a solution to save off screen widgets in an image. But from what I've read this is not possible at the moment.Apoloniaapolune
S
0

This is complete example of taking screenshot and save on the memory. No need any package for taking screenshots

import 'package:path_provider/path_provider.dart';
import 'dart:io';
class WidgetScreenshotExample extends StatefulWidget {
  const WidgetScreenshotExample({super.key});

  @override
  State<WidgetScreenshotExample> createState() =>
      _WidgetScreenshotExampleState();
}

class _WidgetScreenshotExampleState extends State<WidgetScreenshotExample> {
  final globalKey = GlobalKey();

  Future<Uint8List> takeScreenshot() async {
    //Get the render object from context.
    final boundary =
        globalKey.currentContext?.findRenderObject() as RenderRepaintBoundary;
    //Convert to the image
    final image = await boundary.toImage();
    final bytes = await image.toByteData(format: ImageByteFormat.png);
    Uint8List memoryImageData = bytes!.buffer.asUint8List();
    return memoryImageData;
  }

  Future<String> saveImage(Uint8List bytes) async {
    final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
    String path = "";
    try {
      Directory root = await getTemporaryDirectory();
      String directoryPath = '${root.path}/qr_coba';
      // Create the directory if it doesn't exist
      await Directory(directoryPath).create(recursive: true);
      String filePath = '$directoryPath/$timestamp.jpg';
      final file = await File(filePath).writeAsBytes(bytes);
      path = file.path;
    } catch (e) {
      debugPrint(e.toString());
    }
    return path;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RepaintBoundary(
        key: globalKey,
        child: Container(
          color: Colors.red,
          padding: const EdgeInsets.all(20),
          child: const Text('Your Widget'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.save),
        onPressed: () async {
          final imageData = await takeScreenshot();
          await saveImage(imageData);
        },
      ),
    );
  }
}
Shulamite answered 17/5, 2024 at 11:57 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.