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.
This commit is contained in:
parent
037df5f2ff
commit
a043ac41f6
@ -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));
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user