Skip to content

Instantly share code, notes, and snippets.

@AlexV525
Last active October 12, 2025 07:27
Show Gist options
  • Select an option

  • Save AlexV525/612da609aae83cc572d6c051667bf66d to your computer and use it in GitHub Desktop.

Select an option

Save AlexV525/612da609aae83cc572d6c051667bf66d to your computer and use it in GitHub Desktop.
How do we create images through `RepaintBoundary` or `Widget` in Flutter?
// Author: Alex Li (https://github.com/AlexV525)
// Date: 2025/10/10
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
/// Create an image from given [GlobalKey], which is attached to an exist
/// [RepaintBoundary].
///
/// [imageSize] can define what size the generated image will be (in pixels).
Future<Uint8List?> createImageFromRepaintBoundary(
GlobalKey boundaryKey, {
double? pixelRatio,
Size? imageSize,
ui.ImageByteFormat format = ui.ImageByteFormat.png,
}) async {
assert(
boundaryKey.currentContext?.findRenderObject() is RenderRepaintBoundary,
);
final boundary = boundaryKey.currentContext?.findRenderObject() as RenderRepaintBoundary;
final constraints = boundary.constraints;
double? outputRatio = pixelRatio;
if (imageSize != null) {
outputRatio = imageSize.width / constraints.maxWidth;
}
final view = ui.PlatformDispatcher.instance.implicitView!;
final ui.Image image = await boundary.toImage(
pixelRatio: outputRatio ?? MediaQueryData.fromView(view).devicePixelRatio,
);
final byteData = await image.toByteData(
format: format,
);
final imageData = byteData?.buffer.asUint8List();
return imageData;
}
/// 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 view = ui.PlatformDispatcher.instance.implicitView!;
final repaintBoundary = RenderRepaintBoundary();
logicalSize ??= view.physicalSize / view.devicePixelRatio;
imageSize ??= view.physicalSize;
final renderView = RenderView(
view: view,
child: RenderPositionedBox(
alignment: Alignment.center,
child: repaintBoundary,
),
configuration: ViewConfiguration(
physicalConstraints: BoxConstraints.tight(view.physicalSize),
logicalConstraints: BoxConstraints.tight(logicalSize),
devicePixelRatio: view.devicePixelRatio,
),
);
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<void>.delayed(wait);
}
buildOwner.buildScope(rootElement);
buildOwner.finalizeTree();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
final image = await repaintBoundary.toImage(
pixelRatio: imageSize.width / logicalSize.width,
);
final byteData = await image.toByteData(
format: ui.ImageByteFormat.png,
);
final imageData = byteData?.buffer.asUint8List();
return imageData;
}
@HTipi
Copy link

HTipi commented Oct 30, 2023

For Flutter 3.13, it's not working on IOS 17.

@AlexV525
Copy link
Author

For Flutter 3.13, it's not working on IOS 17.

Sounds weird because this is not based on underlying platforms. It could be a problem of Impeller though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment