Skip to content

Instantly share code, notes, and snippets.

@jezell
Created October 6, 2024 15:46
Show Gist options
  • Save jezell/eeb831f7c55a52a7e8954c777851a30c to your computer and use it in GitHub Desktop.
Save jezell/eeb831f7c55a52a7e8954c777851a30c to your computer and use it in GitHub Desktop.
Screenshots
import "package:flutter/material.dart";
class FutureRegistry {
final List<Future> _futures = [];
void register(Future f) {
_futures.add(f);
f.whenComplete(() {
_futures.remove(f);
});
}
Future<void> wait() async {
try {
await Future.wait(_futures);
} catch (_) {
} finally {
if (_futures.isNotEmpty) {
await wait();
}
}
}
static FutureRegistry of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_FutureRegistryData>()!.registry;
}
static FutureRegistry? maybeOf(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_FutureRegistryData>()?.registry;
}
static T track<T extends Future>(BuildContext context, T f) {
FutureRegistry.maybeOf(context)?.register(f);
return f;
}
Widget provide(Widget widget) {
return _FutureRegistryData(registry: this, child: widget);
}
}
class _FutureRegistryData extends InheritedWidget {
const _FutureRegistryData({ required super.child, required this.registry });
final FutureRegistry registry;
@override
bool updateShouldNotify(_FutureRegistryData oldWidget) {
return oldWidget.registry != registry;
}
}
// based on code here, but enhanced and fixed a bit
// https://medium.com/@mabud_alam/take-a-screenshot-of-any-widget-programmatically-in-flutter-4462ef2c4067
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
// ignore: depend_on_referenced_packages
import 'package:flutter_localizations/flutter_localizations.dart';
import 'dart:ui' as ui;
import 'future_registry.dart';
/// We can use this class to convert any widget to an image.
/// It works even with widgets that are not visible on the screen.
class WidgetToImage {
/// Creates an image from the given widget.
///
/// Parameters:
/// - widget: The widget to convert into an image.
/// - wait: Optional duration to wait before capturing the image.
/// - logicalSize: The logical size of the widget. Defaults to the window's physical size divided by the device pixel ratio.
/// - imageSize: The desired size of the output image. Defaults to the window's physical size.
///
/// Returns:
/// A [Uint8List] representing the image data in PNG format, or null if the conversion failed.
static Future<Uint8List?> createImageFromWidget(Widget widget,
{Duration? wait, Size? logicalSize, Size? imageSize}) async {
final view = WidgetsBinding.instance.renderViews.first.flutterView;
final repaintBoundary = RenderRepaintBoundary();
// Calculate logicalSize and imageSize if not provided
logicalSize ??= view.physicalSize / view.devicePixelRatio;
imageSize ??= view.physicalSize;
// Ensure logicalSize and imageSize have the same aspect ratio
assert(logicalSize.aspectRatio == imageSize.aspectRatio,
'logicalSize and imageSize must not be the same');
// Create the render tree for capturing the widget as an image
final renderView = RenderView(
view: view,
child: RenderPositionedBox(
alignment: Alignment.center, child: repaintBoundary),
configuration: ViewConfiguration(
logicalConstraints: BoxConstraints(
minWidth: 0,
minHeight: 0,
maxWidth: logicalSize.width,
maxHeight: logicalSize.height),
devicePixelRatio: 1,
),
);
final pipelineOwner = PipelineOwner();
BuildOwner? buildOwner;
RenderObjectToWidgetElement<RenderBox>? rootElement;
Completer? frame;
buildOwner = BuildOwner(
focusManager: FocusManager(),
onBuildScheduled: () {
frame = Completer();
WidgetsBinding.instance.scheduleFrameCallback((_) {
buildOwner!
..buildScope(rootElement!)
..finalizeTree();
// Flush layout, compositing, and painting operations
pipelineOwner
..flushLayout()
..flushCompositingBits()
..flushPaint();
frame!.complete();
});
});
pipelineOwner.rootNode = renderView;
renderView.prepareInitialFrame();
final registry = FutureRegistry();
final ro = RenderObjectToWidgetAdapter<RenderBox>(
container: repaintBoundary,
child: Directionality(
textDirection: TextDirection.ltr,
child: Localizations(
locale: PlatformDispatcher.instance.locale,
delegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
child: Material(
child: MediaQuery.fromView(
view: view,
child: registry.provide(widget)))),
));
// Attach the widget's render object to the render tree
rootElement = ro.attachToRenderTree(buildOwner);
buildOwner
..buildScope(rootElement)
..finalizeTree();
// Flush layout, compositing, and painting operations
pipelineOwner
..flushLayout()
..flushCompositingBits()
..flushPaint();
// wait for pending futures
await registry.wait();
// Delay if specified
DateTime start = DateTime.now();
do {
if (frame != null) {
await frame!.future;
}
buildOwner
..buildScope(rootElement)
..finalizeTree();
// Flush layout, compositing, and painting operations
pipelineOwner
..flushLayout()
..flushCompositingBits()
..flushPaint();
if (wait != null) {
// Build and finalize the render tree
await Future.delayed(const Duration(milliseconds: 200));
}
if (frame == null) {
await Future.delayed(const Duration(milliseconds: 200));
}
if (wait != null && DateTime.now().difference(start) > wait) {
break;
}
} while (frame == null || frame!.isCompleted);
buildOwner
..buildScope(rootElement)
..finalizeTree();
// Flush layout, compositing, and painting operations
pipelineOwner
..flushLayout()
..flushCompositingBits()
..flushPaint();
// Capture the image and convert it to byte data
final image = await repaintBoundary.toImage(
pixelRatio: imageSize.width / logicalSize.width);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
// Return the image data as Uint8List
image.dispose();
rootElement.visitChildren((Element element) {
// ignore: invalid_use_of_protected_member
rootElement!.deactivateChild(element);
});
buildOwner.finalizeTree();
renderView.dispose();
pipelineOwner.rootNode = null; // renderView
pipelineOwner.dispose();
rootElement.renderObject.dispose();
rootElement.detachRenderObject();
return byteData?.buffer.asUint8List();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment