Is it possible to export hidden widgets as an Image?
Asked Answered
U

3

8

I know how to save visible widgets to an image using RepaintBoundary

What I want is a way to save a widget that is not visible to the user as an image.

Upholster answered 6/7, 2019 at 3:55 Comment(3)
Did you find anything?Shipley
No. But I used kind of a hack to achieve what I want. I put the RepaintBoundary behind all other widgets so that it's not visible to user.Upholster
My problem is that I have a ListView with many items so it just doesn't fit into the screen. I want to make a wide image with all the items visible. Now I'm trying to use zoom: https://mcmap.net/q/495659/-flutter-zoomable-widgetShipley
U
1
/// Creates an image from the given widget by first spinning up a element and render tree,
/// then waiting for the given [wait] amount of time and then creating an image via a [RepaintBoundary].
/// 
/// The final image will be of size [imageSize] and the the widget will be layout, ... with the given [logicalSize].
Future<Uint8List> createImageFromWidget(Widget widget, {Duration wait, Size logicalSize, Size imageSize}) async {
  final RenderRepaintBoundary repaintBoundary = RenderRepaintBoundary();

  logicalSize ??= ui.window.physicalSize / ui.window.devicePixelRatio;
  imageSize ??= ui.window.physicalSize;

  assert(logicalSize.aspectRatio == imageSize.aspectRatio);

  final RenderView renderView = RenderView(
    window: null,
    child: RenderPositionedBox(alignment: Alignment.center, child: repaintBoundary),
    configuration: ViewConfiguration(
      size: logicalSize,
      devicePixelRatio: 1.0,
    ),
  );

  final PipelineOwner pipelineOwner = PipelineOwner();
  final BuildOwner buildOwner = BuildOwner();

  pipelineOwner.rootNode = renderView;
  renderView.prepareInitialFrame();

  final RenderObjectToWidgetElement<RenderBox> rootElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: repaintBoundary,
    child: widget,
  ).attachToRenderTree(buildOwner);

  buildOwner.buildScope(rootElement);

  if (wait != null) {
    await Future.delayed(wait);
  }

  buildOwner.buildScope(rootElement);
  buildOwner.finalizeTree();

  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();

  final ui.Image image = await repaintBoundary.toImage(pixelRatio: imageSize.width / logicalSize.width);
  final ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);

  return byteData.buffer.asUint8List();
}

Read more

Upholster answered 13/4, 2021 at 18:25 Comment(0)
P
1

You can use this code to create image of the widget which is not visible. `

 static Future<Uint8List?> createImageFromWidget(
  BuildContext context, Widget widget,
  {Duration? wait, Size? logicalSize, Size? imageSize}) async {
final repaintBoundary = RenderRepaintBoundary();

logicalSize ??=
    View.of(context).physicalSize / View.of(context).devicePixelRatio;
imageSize ??= View.of(context).physicalSize;

assert(logicalSize.aspectRatio == imageSize.aspectRatio,
    'logicalSize and imageSize must not be the same');

final renderView = RenderView(
    child: RenderPositionedBox(
        alignment: Alignment.center, child: repaintBoundary),
    configuration: ViewConfiguration(
      size: logicalSize,
      devicePixelRatio: 1,
    ),
    view: View.of(context) //PlatformDispatcher.instance.views.first,
    );

final pipelineOwner = PipelineOwner();
final buildOwner = BuildOwner(focusManager: FocusManager());

pipelineOwner.rootNode = renderView;
renderView.prepareInitialFrame();

final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: repaintBoundary,
    child: Directionality(
      textDirection: TextDirection.ltr,
      child: widget,
    )).attachToRenderTree(buildOwner);

buildOwner.buildScope(rootElement);

if (wait != null) {
  await Future.delayed(wait);
}

buildOwner
  ..buildScope(rootElement)
  ..finalizeTree();

pipelineOwner
  ..flushLayout()
  ..flushCompositingBits()
  ..flushPaint();

final image = await repaintBoundary.toImage(
    pixelRatio: imageSize.width / logicalSize.width);
final byteData = await image.toByteData(format: ImageByteFormat.png);

return byteData?.buffer.asUint8List();
}`

For more reference you can visit this link

Purpurin answered 11/2, 2024 at 17:3 Comment(0)
A
0

It works for me but the code has been changed a little for later versions of Dart >= 3.0.0

static Future<Uint8List?> createImageFromWidget(BuildContext context,Widget widget,{Duration? wait, Size? logicalSize, Size? imageSize}) async {
        final repaintBoundary = RenderRepaintBoundary();
    
        logicalSize ??=
            View.of(context).physicalSize / View.of(context).devicePixelRatio;
        imageSize ??= View.of(context).physicalSize;
    
        assert(logicalSize.aspectRatio == imageSize.aspectRatio,
            'logicalSize and imageSize must not be the same');
    
        final renderView = RenderView(
            child: RenderPositionedBox(
                alignment: Alignment.center, child: repaintBoundary),
            configuration: ViewConfiguration(
              logicalConstraints: BoxConstraints.tight(logicalSize),
              devicePixelRatio: View.of(context).devicePixelRatio,
            ),
            view: View.of(context));
    
        final pipelineOwner = PipelineOwner();
        final buildOwner = BuildOwner(focusManager: FocusManager());
    
        pipelineOwner.rootNode = renderView;
        renderView.prepareInitialFrame();
    
        final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
            container: repaintBoundary,
            child: Directionality(
              textDirection: ui.TextDirection.ltr,
              child: widget,
            )).attachToRenderTree(buildOwner);
    
        buildOwner.buildScope(rootElement);
    
        if (wait != null) {
          await Future.delayed(wait);
        }
    
        buildOwner
          ..buildScope(rootElement)
          ..finalizeTree();
    
        pipelineOwner
          ..flushLayout()
          ..flushCompositingBits()
          ..flushPaint();
    
        final image = await repaintBoundary.toImage(
            pixelRatio: imageSize.width / logicalSize.width);
        final byteData = await image.toByteData(format: ImageByteFormat.png);
    
        return byteData?.buffer.asUint8List();
      }

With this you will able to Capture the widget, and if you want share it. In my case with share_plus

  Future<void> _captureAndShare() async {
    try {

      Widget qrScreen = QrScreen();// Your Widget

      Size logicalSize = const Size(800, 1280);
      Size imageSize = const Size(800, 1280);

      // Needs a Size
      qrScreen = MediaQuery(
        data: MediaQueryData(size: logicalSize),
        child: qrScreen,
      );

      Uint8List? imageUint = await createImageFromWidget(
        context,
        qrScreen,
        wait: const Duration(milliseconds: 20),
        imageSize: imageSize,
        logicalSize: logicalSize,
      );

      if (imageUint == null) {
        print('Failed to capture: the image is null.');
        return;
      }

      // Save the image in a temp file
      Directory tempDir = await getTemporaryDirectory();
      File file = await File('${tempDir.path}/shared_image.png').create();
      await file.writeAsBytes(imageUint);

      // Share the image
      XFile xFile = XFile(file.path);
      await Share.shareXFiles([xFile], text: "Hello world");
    } catch (e) {
      print('Failed to capture and share the image: $e');
    }
  }
Amandie answered 5/8, 2024 at 19:47 Comment(2)
Will this code work with a dynamically sized widget?Upholster
I should, in the example the size is static, but you can give the device size with MediaQuery.of(context).size.height/width in logicalSize and imageSizeAmandie

© 2022 - 2025 — McMap. All rights reserved.