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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// 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/animation.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -2453,6 +2455,45 @@ class RenderRepaintBoundary extends RenderProxyBox {
|
|||||||
@override
|
@override
|
||||||
bool get isRepaintBoundary => true;
|
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
|
/// 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
|
/// 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
|
/// 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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'dart:ui' as ui show Image;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@ -15,9 +18,9 @@ void main() {
|
|||||||
RenderFittedBox makeFittedBox() {
|
RenderFittedBox makeFittedBox() {
|
||||||
return new RenderFittedBox(
|
return new RenderFittedBox(
|
||||||
child: new RenderCustomPaint(
|
child: new RenderCustomPaint(
|
||||||
painter: new TestCallbackPainter(
|
painter: new TestCallbackPainter(onPaint: () {
|
||||||
onPaint: () { painted = true; }
|
painted = true;
|
||||||
),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -134,4 +137,57 @@ void main() {
|
|||||||
debugDefaultTargetPlatformOverride = null;
|
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