New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow flutter to save widget to image even if widget is offscreen #40064
Comments
@tapizquent Please provide a small self-contained app that demonstrates the problem. You can include small examples inline if they're enclosed by backticks, or just provide a public link to a github "gist" - https://gist.github.com/. |
@BondarenkoStas I apologize, I have added a main.dart you can run simulating the scenario. Container with Placeholder act as any widget you'd like to "photograph". I added the Offstage option as a recommendation coming from: https://stackoverflow.com/questions/55134343/renderrepaintboundary-to-image-without-adding-widget-to-screen#comment102118492_55134343 which should "technically" work , but I also tried it hiding the widget by setting Opacity to 0 but that doesn't work either. |
This also bothers me, i want to create thumbnails using Widgets but they wont be visible, so i tought about using There should be a way to do this |
The problem lies in the way the You could create a separate |
I'll try to come up with a function like |
Any luck guys ?? I also want content of a container to be saved as image. found an article here if anyone of you can understand whats happening ? |
Here we go:
|
@christian-muertz Thanks, It worked fine. |
Getting the code @christian-muertz but I'm getting the following error at the line
Error:
|
@KieranLafferty please share your |
@christian-muertz Wow.. It is working..
|
I have a padding widget with a row and two image.asset inside it.
|
@abdulllrashid What do you mean by the whole Widget thing except the image does not load? I don't see anything that should be shown except the two images in a row with some padding. Could you add the screenshots of the first call and the second? |
@christian-muertz I'm expecting two images to be rendered but unfortunately, it doesn't show on the first render. On second call it does. the issue got fixed when I added a 1-sec wait in the function, earlier it was null |
I can only see a part of image being generated ... which is frustrating .. |
More detailed information would be very helpful |
Hello, I've got a problem with images too, too. I'll try to create a swipe card but is laggy. I try to change the widget to raster object (image widget) when it is dragging to eliminate lags. But my images disappear (background of card and button). Or is loading after start pan not earlier (at the build widget moment). Do you have maybe any solution for this? |
I've been partially successful with the createImageFromWidget function above (thank you!), but now I've hit a problem where the Focus (onKey: ...) method is no longer called! Context: This is a desktop application running on Mac OS. The keyboard interactions call the Focus onKey method in my displayed view fine, but as soon as I pass even the simplest widget to createImageFromWidget() the onKey method is no longer called on keyboard input. TextFields are working, Focus is working (onFocusChange works), but not onKey. I've tried re-building the screen after the call with no luck - only a restart of the application bring back onKey. Anyone any ideas? |
OK, I found the problem with the keyboard. createImageFromWidget creates a BuildOwner. BuildOwner creates a new FocusManager, which in it's constructor calls:
To work around this you can save the original keyEventHandler ( It feels like a hack, but it fixes the issue! |
Thanks for this @christian-muertz What is the correct way of disposing of the widget that is passed into |
This code works like a charm until i try to pass a SvgPicture as a widget. Those Svgs have been a pain in the ass for a long time, maybe you could advice something? |
If the child widget is SingleChildScrollView, it won't work |
I am using interactive view widget and it only exports the part which is visible on viewport. In other words, if a widget or part of a widget is out of viewport (or not rendered yet) it won't print out the image as a whole big picture. Thumbs up if you have the same problem (for ex: list view, page view, stack) |
There is a package called "Screenshot". It can truly get the screenshot of a widget even if the some parts of the widget is out of the screen (or not rendered yet). I basically wrapped my InteractiveView widget with it. The screen width is 1440px but the InteractiveView has a child with width 10000px. So it can handle this. Easy to set up. I used it combining with PDF and Printing packages to export as image and pdf. |
Hi, I tried this, it works and captures screenshots of widgets that are not rendered on the screen. However, when I add
I tried using Screenshot package too, but the screenshot captures only the widgets which are rendered on the screen and throws a In my case I need each ListView Item to be an individual screenshot and not the ListView as a whole. |
@LaysDragon So removing the |
Till now didn't encounter any other problem. It seems the bug is caused by a old fix code not work on a new version flutter. It even trigger more warning message about shouldn't access this internal field that only reserved for compatibility reason. |
|
About item 1, after some time using my implementation, the100ms delay I use doesn't work always, but I still have no clue on why's that. Also, I didn't figure out any workarounds yet. |
I am trying to reference the widget by Key like so :
but
throws an error :
It seems like the referenced widget gets taken out of the original widget tree. |
I am using the screenshot package and adapted it a bit (it is based on these code examples here). I would like to capture only the child but the rendered image is either the ScreenSize or the passed ImageSize. I tried setting another RepaintBoundary and to get it I used:
but the currentContext is always null. Any hints how to handle my problem? |
On flutter web it throwing below exception. ServicesBinding.instance!.keyEventManager.keyMessageHandler == null |
did you find the solution?? |
I am capturing a widget of QR with Logo in the center but when i capture the widget either with plugin screenshot or by using Future createImageFromWidget(Widget widget, {Duration wait, Size logicalSize, Size imageSize}) i am getting white screen.. final qrView = MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(200),
child: QrImage(
data: 'www.google.com',
gapless: false,
version: QrVersions.auto,
// i have tried all imageproviders e.g [AssetImage], [CachedNetworkImageProvider], [NetworkImage]
embeddedImage: NetworkImage(
'https://images.crazygames.com/colorpixelartclassic.png?auto=format,compress&q=75&cs=strip&ch=DPR&w=1200&h=630&fit=crop, // any network image
),
embeddedImageEmitsError: true,
errorStateBuilder: (context, error) => Center(
child: Text(
error.toString(),
),
),
),
),
),
),
);
final capturedImage = await screenshotController.captureFromWidget(
qrView,
delay: const Duration(seconds: 1),
); |
have you found a solution of get the image size |
According to now January 27, 2023 working code is below
|
This appears to be one of the most underrated features requests there are. Beside @tapizquent usecase, there's another very common one: Sooner or later, every app needs some screenshots for its store listings. Creating those is quite labor intense:
Now imagine you need five screenshots, in two languages, shown on six devices: 60 screenshots to create - that's quite The snippet above is a start, and distinguishing between logical and image size makes it incredible versatile. @christian-muertz thank you very much for providing it! Unfortunately it has its limitations (on some apps the first screenshot fails - subsequent screenshots succeed, assertions in debug mode, context loses translations/providers, difficulties with global keys, ...) that require quite some workarounds before it delivers the expected results... Hence my vote to increase the priority of this feature request. What do you think? |
An updated version, that works on Flutter 3.10.x, and which removes the dependance on ui.window, since soon it will be deprecated
|
Not working for me on Flutter 3.10 (tested on Android Emulator). My custom widget is a SingleChildScrollView |
Also, please note that if you intend to invoke this method many times, make sure that the PipelineOwner object was created only once. Otherwise, your app will crash. |
@zhuhean Are you saying that Why does the app crash if you declare a new |
Why does the |
Just wanted to drop this code here in case anyone needs to render to a var recorder = PictureRecorder();
var canvas = Canvas(recorder);
canvas.drawImage(Offset.zero, myImage);
canvas.transform( ... some transformation ...)
var picture = recorder.endRecording();
picture.toImage(100, 100).then((ui.Image) {
var png = ui.Image.toByteData(format: ... )
} Thanks to @jonahwilliams for the example code. |
To take screenshots with 'ListView' widget inside, just simply wrap a MediaQuery widget around the Directionality widget. Tested and it works fine on Flutter 3.13.2 (Since ListView behaves differently on Flutter 3.13, it does a lookup for View.of(context).devicePixelRatio in Scrollable widget (One of ListView dependency widget)
if we wrap MediaQuery it does not need to lookup for View.of(context) widget anymore hence make it work fine For example:
|
did you find any solution? |
Hi, @christian-muertz. I am just curious how do you create this code or from where did you take it? :)
|
Using the method from @christian-muertz with a |
Guys, how can we increase image quality in this? |
Thank you very much for your help! import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:jpeg_encode/jpeg_encode.dart';
Future<Uint8List?> capture(
Widget child, {
required Size size,
}) async {
final logicalSize = size;
final imageSize = size;
final repaintBoundary = RenderRepaintBoundary();
final renderView = RenderView(
child: RenderPositionedBox(alignment: Alignment.center, child: repaintBoundary),
configuration: ViewConfiguration(
size: logicalSize,
devicePixelRatio: 1,
),
view: 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: MediaQuery(
data: MediaQuery.of(rootNavigatorKey.currentContext!).copyWith(),
child: Container(
color: Colors.white,
width: logicalSize.width,
height: logicalSize.height,
child: child,
),
),
),
).attachToRenderTree(
buildOwner,
);
buildOwner.buildScope(rootElement);
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.rawRgba);
print('imageSize.height : ${imageSize.height}');
print('image.height : ${image.height}');
return JpegEncoder().compress(byteData!.buffer.asUint8List(), image.width, image.height, 100);
} And with the GoRouter key available: import 'package:go_router/go_router.dart';
final rootNavigatorKey = GlobalKey<NavigatorState>();
// GoRouter configuration
final router = GoRouter(
navigatorKey: rootNavigatorKey,
initialLocation: '/',
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return HomeView(navigationShell: navigationShell);
},
// ... Then use it like this: final image = await capture(widget, size: const Size(2048, 512)); |
Hi @Bahrom2101, you can use Provider/Riverpod or something like this to create a separated class that hold your changes. class YourNotifierProvider extends ChangeNotifier {
double _itemWidth = 1;
double get itemWidth => _itemWidth;
set itemWidth(double value) {
_itemWidth = value;
notifyListeners();
}
}
/// Your stateful widget
class YourWidget extends StatefulWidget {
const YourWidget({super.key});
@override
State<YourWidget> createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
@override
Widget build(BuildContext context) {
final notifier = context.watch<YourNotifierProvider>();
final itemWidth = notifier.itemWidth;
return IconButton(
onPressed: () {
context.read<YourNotifierProvider>().itemWidth = itemWidth + 1;
},
icon: Text(notifier.itemWidth.toString()),
);
}
}
/// Your async capture method
Future<Uint8List> captureMyWidget() async {
final widget = MultiProvider(
providers: [
ChangeNotifierProvider<YourNotifierProvider>.value(value: yourNotifierProviderInstance),
],
child: YourWidget(),
);
// Capture the widget
return capture(widget, size: const Size(2048, 512));
} When you press the IconButton, you should increment the value of itemWidth, then your provider should hold the changes and you can now screenshot it. |
When you have an application, sometimes it's important to save widgets as images even if they're not on screen.
An example of how this would be necessary: having a customized widget displayed in a thumbnail in the app but you wish to save as an image a full size/full screen version of that same widget.
Steps to Reproduce
_exportLayoutKey.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
Logs
It will throw an error
flutter: 'package:flutter/src/rendering/proxy_box.dart': Failed assertion: line 2882 pos 12: '!debugNeedsPaint': is not true.
running
flutter doctor -v
Update:
Minimal project showcasing scenario:
I have tried doing it like this, and also switching Offstage and RepaintBoundary:
The text was updated successfully, but these errors were encountered: