Skip to content

Instantly share code, notes, and snippets.

@Gorniv
Forked from PlugFox/example.dart
Created October 29, 2021 15:02
Show Gist options
  • Save Gorniv/bd740fb0bed93087c9ee20c19b9c6cea to your computer and use it in GitHub Desktop.
Save Gorniv/bd740fb0bed93087c9ee20c19b9c6cea to your computer and use it in GitHub Desktop.
Flutter get screenshot from canvas layer
// Just example how to save screenshot
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:gallery_saver/gallery_saver.dart';
@override
Future<void> saveScreenshot() async {
final screenshot = await screenshotController.takeScreenshot();
final dir = await getTemporaryDirectory();
final file = File(join(dir.path, 'screenshot_${DateTime.now().toIso8601String()}.png'));
await file.writeAsBytes(
screenshot.buffer.asUint8List(screenshot.offsetInBytes, screenshot.lengthInBytes),
);
await GallerySaver.saveImage(file.path).then<void>(
(success) {
if (success != true) {
throw UnsupportedError('Sad story');
}
},
);
print('Well done');
}
import 'dart:typed_data' as td;
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
/// Scope to create a screenshot of the underlying layer of the canvas "tree" of widgets
/// You can interact through the [ScreenshotController] - for example, from the business logic layer
@immutable
class ScreenshotScope extends StatelessWidget {
final Widget child;
final ScreenshotController? controller;
const ScreenshotScope({
required this.child,
this.controller,
Key? key,
}) : super(key: key);
/// Take a screenshot from [ScreenshotScope]
static Future<td.ByteData> takeScreenshot(BuildContext context) => _of(context).takeScreenshot();
/// Get [_ScreenshotScopeState] state from context
static ScreenshotScopeState _of(BuildContext context) => context.findAncestorStateOfType<ScreenshotScopeState>() ?? _notInScope();
@alwaysThrows
static Never _notInScope() => throw UnsupportedError('Not in ScreenshotScope scope');
@override
Widget build(BuildContext context) => RepaintBoundary(
child: _ScreenshotScope(
controller: controller,
child: child,
),
);
}
@immutable
class _ScreenshotScope extends StatefulWidget {
final Widget child;
final ScreenshotController? controller;
const _ScreenshotScope({
required this.child,
this.controller,
Key? key,
}) : super(key: key);
@override
State<_ScreenshotScope> createState() => ScreenshotScopeState();
}
class ScreenshotScopeState extends State<_ScreenshotScope> {
@override
void initState() {
super.initState();
widget.controller?.addCallback(takeScreenshot);
}
@override
void didUpdateWidget(_ScreenshotScope oldWidget) {
super.didUpdateWidget(oldWidget);
if (!identical(widget.controller, oldWidget.controller)) {
widget.controller?.addCallback(takeScreenshot);
}
}
Future<td.ByteData> takeScreenshot() async {
final boundary = context.findAncestorRenderObjectOfType<RenderRepaintBoundary>();
if (boundary is! RenderRepaintBoundary) {
throw UnsupportedError('Nearest RenderObject not RenderRepaintBoundary');
}
final image = await boundary.toImage(pixelRatio: 1);
final bytes = await image.toByteData(format: ui.ImageByteFormat.png);
if (bytes is! td.ByteData) {
throw UnsupportedError('Error while converting screenshot image to byte array');
}
return bytes;
}
@override
Widget build(BuildContext context) => widget.child;
}
/// Controller for creating a screenshot of the underlying tree
/// One instance can be installed only to one scope at a time
@sealed
@immutable
class ScreenshotController {
/// Factory for creating a controller
factory ScreenshotController({String? debugId}) = ScreenshotController._;
/// Factory for creating / getting a canonic literal
@literal
const factory ScreenshotController.literal({required String debugId}) = ScreenshotController._literal;
// ignore: prefer_const_constructors_in_immutables
ScreenshotController._({this.debugId});
@literal
const ScreenshotController._literal({required this.debugId});
/// The main controller, a canonical instance literal, can be used as a singleton
static const ScreenshotController main = ScreenshotController.literal(debugId: 'main');
@protected
static final Expando<Future<td.ByteData> Function()> _callbacks = Expando<Future<td.ByteData> Function()>();
/// Controller ID for debugging purposes
/// is also useful for creating multiple canonic literals
@internal
@protected
@nonVirtual
@visibleForTesting
final String? debugId;
@mustCallSuper
Future<td.ByteData> takeScreenshot() => _callbacks[this]?.call() ?? _missingWidget();
@alwaysThrows
Never _missingWidget() => throw UnsupportedError('The controller was not installed to the corresponding ScreenshotScope');
@internal
@protected
@nonVirtual
@visibleForTesting
void addCallback(Future<td.ByteData> Function() takeScreenshot) => _callbacks[this] = takeScreenshot;
@override
bool operator ==(Object other) => identical(other, this);
@override
int get hashCode => debugId?.hashCode ?? super.hashCode;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment