diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index 386c90e423..031b532843 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart @@ -2,8 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:collection'; -import 'dart:ui' as ui show ImageFilter, Picture, SceneBuilder; +import 'dart:ui' as ui show Image, ImageFilter, Picture, Scene, SceneBuilder; import 'dart:ui' show Offset; import 'package:flutter/foundation.dart'; @@ -515,6 +516,41 @@ class OffsetLayer extends ContainerLayer { super.debugFillProperties(properties); properties.add(new DiagnosticsProperty('offset', offset)); } + + /// Capture an image of the current state of this layer and its children. + /// + /// The returned [ui.Image] has uncompressed raw RGBA bytes, in the + /// dimensions [logicalSize] multiplied by the [pixelRatio]. + /// + /// The [pixelRatio] describes the scale between the logical pixels and the + /// size of the output image. It is independent of the + /// [window.devicePixelRatio] for the device, so specifying 1.0 (the default) + /// will give you a 1:1 mapping between logical pixels and the output pixels + /// in the image. + /// + /// See also: + /// + /// * [RenderRepaintBoundary.toImage] for a similar API at the render object level. + /// * [dart:ui.Scene.toImage] for more information about the image returned. + Future toImage(Size logicalSize, {double pixelRatio: 1.0}) async { + assert(pixelRatio != null); + final ui.SceneBuilder builder = new ui.SceneBuilder(); + final Matrix4 transform = new Matrix4.diagonal3Values(pixelRatio, pixelRatio, 1.0); + transform.translate(-offset.dx, -offset.dy, 0.0); + builder.pushTransform(transform.storage); + addToScene(builder, Offset.zero); + final ui.Scene scene = builder.build(); + try { + // Size is rounded up to the next pixel to make sure we don't clip off + // anything. + return await scene.toImage( + (pixelRatio * logicalSize.width).ceil(), + (pixelRatio * logicalSize.height).ceil(), + ); + } finally { + scene.dispose(); + } + } } /// A composite layer that clips its children using a rectangle. diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index d06f61775d..58bac8a644 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -4,7 +4,7 @@ import 'dart:async'; -import 'dart:ui' as ui show ImageFilter, Gradient, SceneBuilder, Scene, Image; +import 'dart:ui' as ui show ImageFilter, Gradient, Image; import 'package:flutter/animation.dart'; import 'package:flutter/foundation.dart'; @@ -2470,27 +2470,52 @@ class RenderRepaintBoundary extends RenderProxyBox { /// will give you a 1:1 mapping between logical pixels and the output pixels /// in the image. /// + /// ## Sample code + /// + /// The following is an example of how to go from a `GlobalKey` on a + /// `RepaintBoundary` to a PNG: + /// + /// ```dart + /// class PngHome extends StatefulWidget { + /// PngHome({Key key}) : super(key: key); + /// + /// @override + /// _PngHomeState createState() => new _PngHomeState(); + /// } + /// + /// class _PngHomeState extends State { + /// GlobalKey globalKey = new GlobalKey(); + /// + /// Future _capturePng() async { + /// RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject(); + /// ui.Image image = await boundary.toImage(); + /// ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png); + /// Uint8List pngBytes = byteData.buffer.asUint8List(); + /// print(pngBytes); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return RepaintBoundary( + /// key: globalKey, + /// child: Center( + /// child: FlatButton( + /// child: Text('Hello World', textDirection: TextDirection.ltr), + /// onPressed: _capturePng, + /// ), + /// ), + /// ); + /// } + /// } + /// ``` + /// /// See also: /// + /// * [OffsetLayer.toImage] for a similar API at the layer level. /// * [dart:ui.Scene.toImage] for more information about the image returned. - Future toImage({double pixelRatio: 1.0}) async { + Future toImage({double pixelRatio: 1.0}) { assert(!debugNeedsPaint); - final ui.SceneBuilder builder = new ui.SceneBuilder(); - final Matrix4 transform = new Matrix4.diagonal3Values(pixelRatio, pixelRatio, 1.0); - transform.translate(-layer.offset.dx, -layer.offset.dy, 0.0); - builder.pushTransform(transform.storage); - layer.addToScene(builder, Offset.zero); - final ui.Scene scene = builder.build(); - try { - // Size is rounded up to the next pixel to make sure we don't clip off - // anything. - return await scene.toImage( - (pixelRatio * size.width).ceil(), - (pixelRatio * size.height).ceil(), - ); - } finally { - scene.dispose(); - } + return layer.toImage(size, pixelRatio: pixelRatio); }