Unverified Commit a043ac41 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Adding an API for capturing an image of a RenderRepaintBoundary. (#16758)

This adds a toImage function to RenderRepaintBoundary that returns an uncompressed raw image of the RenderRepaintBoundary and its children. A device pixel ratio different from the physical ratio may be specified for the captured image. A value of 1.0 will give an image in logical pixels.
parent 037df5f2
......@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui show ImageFilter, Gradient;
import 'dart:async';
import 'dart:ui' as ui show ImageFilter, Gradient, SceneBuilder, Scene, Image;
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
......@@ -2453,6 +2455,45 @@ class RenderRepaintBoundary extends RenderProxyBox {
@override
bool get isRepaintBoundary => true;
/// Capture an image of the current state of this render object and its
/// children.
///
/// The returned [ui.Image] has uncompressed raw RGBA bytes in the dimensions
/// of the render object, multiplied by the [pixelRatio].
///
/// To use [toImage], the render object must have gone through the paint phase
/// (i.e. [debugNeedsPaint] must be false).
///
/// 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:
///
/// * [dart:ui.Scene.toImage] for more information about the image returned.
Future<ui.Image> toImage({double pixelRatio: 1.0}) async {
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();
}
}
/// The number of times that this render object repainted at the same time as
/// its parent. Repaint boundaries are only useful when the parent and child
/// paint at different times. When both paint at the same time, the repaint
......
......@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'dart:ui' as ui show Image;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
......@@ -15,9 +18,9 @@ void main() {
RenderFittedBox makeFittedBox() {
return new RenderFittedBox(
child: new RenderCustomPaint(
painter: new TestCallbackPainter(
onPaint: () { painted = true; }
),
painter: new TestCallbackPainter(onPaint: () {
painted = true;
}),
),
);
}
......@@ -134,4 +137,57 @@ void main() {
debugDefaultTargetPlatformOverride = null;
});
});
test('RenderRepaintBoundary can capture images of itself', () async {
RenderRepaintBoundary boundary = new RenderRepaintBoundary();
layout(boundary, constraints: new BoxConstraints.tight(const Size(100.0, 200.0)));
pumpFrame(phase: EnginePhase.composite);
ui.Image image = await boundary.toImage();
expect(image.width, equals(100));
expect(image.height, equals(200));
// Now with pixel ratio set to something other than 1.0.
boundary = new RenderRepaintBoundary();
layout(boundary, constraints: new BoxConstraints.tight(const Size(100.0, 200.0)));
pumpFrame(phase: EnginePhase.composite);
image = await boundary.toImage(pixelRatio: 2.0);
expect(image.width, equals(200));
expect(image.height, equals(400));
// Try building one with two child layers and make sure it renders them both.
boundary = new RenderRepaintBoundary();
final RenderStack stack = new RenderStack()..alignment = Alignment.topLeft;
final RenderDecoratedBox blackBox = new RenderDecoratedBox(
decoration: const BoxDecoration(color: const Color(0xff000000)),
child: new RenderConstrainedBox(
additionalConstraints: new BoxConstraints.tight(const Size.square(20.0)),
));
stack.add(new RenderOpacity()
..opacity = 0.5
..child = blackBox);
final RenderDecoratedBox whiteBox = new RenderDecoratedBox(
decoration: const BoxDecoration(color: const Color(0xffffffff)),
child: new RenderConstrainedBox(
additionalConstraints: new BoxConstraints.tight(const Size.square(10.0)),
));
final RenderPositionedBox positioned = new RenderPositionedBox(
widthFactor: 2.0,
heightFactor: 2.0,
alignment: Alignment.topRight,
child: whiteBox,
);
stack.add(positioned);
boundary.child = stack;
layout(boundary, constraints: new BoxConstraints.tight(const Size(20.0, 20.0)));
pumpFrame(phase: EnginePhase.composite);
image = await boundary.toImage();
expect(image.width, equals(20));
expect(image.height, equals(20));
final ByteData data = await image.toByteData();
expect(data.lengthInBytes, equals(20 * 20 * 4));
expect(data.elementSizeInBytes, equals(1));
const int stride = 20 * 4;
expect(data.getUint32(0), equals(0x00000080));
expect(data.getUint32(stride - 4), equals(0xffffffff));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment