未验证 提交 93cb5dbb 编写于 作者: F Ferhat 提交者: GitHub

[web] Implement Canvas.drawPicture (#24574)

上级 e9c676f1
repository: https://github.com/flutter/goldens.git repository: https://github.com/flutter/goldens.git
revision: 041056fc3ae057574586fa6f69b2cc9465c4a5bf revision: bb55871d3803337053f7200b8690a4c1322e82ea
...@@ -923,12 +923,6 @@ class BitmapCanvas extends EngineCanvas { ...@@ -923,12 +923,6 @@ class BitmapCanvas extends EngineCanvas {
_closeCurrentCanvas(); _closeCurrentCanvas();
} }
/// Paints the [picture] into this canvas.
void drawPicture(ui.Picture picture) {
final EnginePicture enginePicture = picture as EnginePicture;
enginePicture.recordingCanvas!.apply(this, bounds);
}
/// Draws vertices on a gl context. /// Draws vertices on a gl context.
/// ///
/// If both colors and textures is specified in paint data, /// If both colors and textures is specified in paint data,
......
...@@ -444,8 +444,7 @@ class SurfaceCanvas implements ui.Canvas { ...@@ -444,8 +444,7 @@ class SurfaceCanvas implements ui.Canvas {
void drawPicture(ui.Picture picture) { void drawPicture(ui.Picture picture) {
// ignore: unnecessary_null_comparison // ignore: unnecessary_null_comparison
assert(picture != null); // picture is checked on the engine side assert(picture != null); // picture is checked on the engine side
// TODO(het): Support this _canvas.drawPicture(picture);
throw UnimplementedError();
} }
@override @override
......
...@@ -120,13 +120,25 @@ class RecordingCanvas { ...@@ -120,13 +120,25 @@ class RecordingCanvas {
_recordingEnded = true; _recordingEnded = true;
} }
/// Applies the recorded commands onto an [engineCanvas]. /// Applies the recorded commands onto an [engineCanvas] and signals to
/// canvas that all painting is completed for garbage collection/reuse.
/// ///
/// The [clipRect] specifies the clip applied to the picture (screen clip at /// The [clipRect] specifies the clip applied to the picture (screen clip at
/// a minimum). The commands that fall outside the clip are skipped and are /// a minimum). The commands that fall outside the clip are skipped and are
/// not applied to the [engineCanvas]. A command must have a non-zero /// not applied to the [engineCanvas]. A command must have a non-zero
/// intersection with the clip in order to be applied. /// intersection with the clip in order to be applied.
void apply(EngineCanvas engineCanvas, ui.Rect clipRect) { void apply(EngineCanvas engineCanvas, ui.Rect clipRect) {
applyCommands(engineCanvas, clipRect);
engineCanvas.endOfPaint();
}
/// Applies the recorded commands onto an [engineCanvas].
///
/// The [clipRect] specifies the clip applied to the picture (screen clip at
/// a minimum). The commands that fall outside the clip are skipped and are
/// not applied to the [engineCanvas]. A command must have a non-zero
/// intersection with the clip in order to be applied.
void applyCommands(EngineCanvas engineCanvas, ui.Rect clipRect) {
assert(_recordingEnded); assert(_recordingEnded);
if (_debugDumpPaintCommands) { if (_debugDumpPaintCommands) {
final StringBuffer debugBuf = StringBuffer(); final StringBuffer debugBuf = StringBuffer();
...@@ -183,7 +195,6 @@ class RecordingCanvas { ...@@ -183,7 +195,6 @@ class RecordingCanvas {
} }
} }
} }
engineCanvas.endOfPaint();
} }
/// Prints recorded commands. /// Prints recorded commands.
...@@ -511,6 +522,29 @@ class RecordingCanvas { ...@@ -511,6 +522,29 @@ class RecordingCanvas {
_commands.add(command); _commands.add(command);
} }
void drawPicture(ui.Picture picture) {
assert(!_recordingEnded);
final EnginePicture enginePicture = picture as EnginePicture;
// TODO apply renderStrategy of picture recording to this recording.
if (enginePicture.recordingCanvas == null) {
// No contents / nothing to draw.
return;
}
final RecordingCanvas pictureRecording = enginePicture.recordingCanvas!;
if (pictureRecording._didDraw == true) {
_didDraw = true;
}
renderStrategy.merge(pictureRecording.renderStrategy);
// Need to save to make sure we don't pick up leftover clips and
// transforms from running commands in picture.
save();
_commands.addAll(pictureRecording._commands);
restore();
if (pictureRecording._pictureBounds != null) {
_paintBounds.growBounds(pictureRecording._pictureBounds!);
}
}
void drawImageRect( void drawImageRect(
ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaint paint) { ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaint paint) {
assert(!_recordingEnded); assert(!_recordingEnded);
...@@ -1746,7 +1780,8 @@ class _PaintBounds { ...@@ -1746,7 +1780,8 @@ class _PaintBounds {
growLTRB(r.left, r.top, r.right, r.bottom, command); growLTRB(r.left, r.top, r.right, r.bottom, command);
} }
/// Grow painted area to include given rectangle. /// Grow painted area to include given rectangle and precompute
/// clipped out state for command.
void growLTRB(double left, double top, double right, double bottom, void growLTRB(double left, double top, double right, double bottom,
DrawCommand command) { DrawCommand command) {
if (left == right || top == bottom) { if (left == right || top == bottom) {
...@@ -1826,6 +1861,52 @@ class _PaintBounds { ...@@ -1826,6 +1861,52 @@ class _PaintBounds {
_didPaintInsideClipArea = true; _didPaintInsideClipArea = true;
} }
/// Grow painted area to include given rectangle.
void growBounds(ui.Rect bounds) {
final double left = bounds.left;
final double top = bounds.top;
final double right = bounds.right;
final double bottom = bounds.bottom;
if (left == right || top == bottom) {
return;
}
double transformedPointLeft = left;
double transformedPointTop = top;
double transformedPointRight = right;
double transformedPointBottom = bottom;
if (!_currentMatrixIsIdentity) {
_tempRectData[0] = left;
_tempRectData[1] = top;
_tempRectData[2] = right;
_tempRectData[3] = bottom;
transformLTRB(_currentMatrix, _tempRectData);
transformedPointLeft = _tempRectData[0];
transformedPointTop = _tempRectData[1];
transformedPointRight = _tempRectData[2];
transformedPointBottom = _tempRectData[3];
}
if (_didPaintInsideClipArea) {
_left = math.min(
math.min(_left, transformedPointLeft), transformedPointRight);
_right = math.max(
math.max(_right, transformedPointLeft), transformedPointRight);
_top =
math.min(math.min(_top, transformedPointTop), transformedPointBottom);
_bottom = math.max(
math.max(_bottom, transformedPointTop), transformedPointBottom);
} else {
_left = math.min(transformedPointLeft, transformedPointRight);
_right = math.max(transformedPointLeft, transformedPointRight);
_top = math.min(transformedPointTop, transformedPointBottom);
_bottom = math.max(transformedPointTop, transformedPointBottom);
}
_didPaintInsideClipArea = true;
}
void saveTransformsAndClip() { void saveTransformsAndClip() {
_transforms.add(_currentMatrix.clone()); _transforms.add(_currentMatrix.clone());
_clipStack.add(_clipRectInitialized _clipStack.add(_clipRectInitialized
...@@ -1942,4 +2023,11 @@ class RenderStrategy { ...@@ -1942,4 +2023,11 @@ class RenderStrategy {
bool isInsideShaderMask = false; bool isInsideShaderMask = false;
RenderStrategy(); RenderStrategy();
/// Merges render strategy settings from a child recording.
void merge(RenderStrategy childStrategy) {
hasImageElements |= childStrategy.hasImageElements;
hasParagraphs |= childStrategy.hasParagraphs;
hasArbitraryPaint |= childStrategy.hasArbitraryPaint;
}
} }
...@@ -31,61 +31,90 @@ void testMain() async { ...@@ -31,61 +31,90 @@ void testMain() async {
await webOnlyFontCollection.ensureFontsLoaded(); await webOnlyFontCollection.ensureFontsLoaded();
}); });
test('draw growing picture across frames', () async { group('Add picture to scene', () {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); test('draw growing picture across frames', () async {
builder.pushClipRect( final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
const Rect.fromLTRB(0, 0, 100, 100), builder.pushClipRect(
); const Rect.fromLTRB(0, 0, 100, 100),
);
_drawTestPicture(builder, 100, false);
builder.pop(); _drawTestPicture(builder, 100, false);
builder.pop();
html.Element elm1 = builder.build().webOnlyRootElement;
html.document.body.append(elm1); html.Element elm1 = builder
.build()
// Now draw picture again but at larger size. .webOnlyRootElement;
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder(); html.document.body.append(elm1);
builder2.pushClipRect(
const Rect.fromLTRB(0, 0, 100, 100), // Now draw picture again but at larger size.
); final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
// Now draw the picture at original target size, which will use a builder2.pushClipRect(
// different code path that should normally not have width/height set const Rect.fromLTRB(0, 0, 100, 100),
// on image element. );
_drawTestPicture(builder2, 20, false); // Now draw the picture at original target size, which will use a
builder2.pop(); // different code path that should normally not have width/height set
// on image element.
elm1.remove(); _drawTestPicture(builder2, 20, false);
html.document.body.append(builder2.build().webOnlyRootElement); builder2.pop();
await matchGoldenFile('canvas_draw_picture_acrossframes.png', elm1.remove();
region: region); html.document.body.append(builder2
}); .build()
.webOnlyRootElement);
test('draw growing picture across frames clipped', () async {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); await matchGoldenFile('canvas_draw_picture_acrossframes.png',
builder.pushClipRect( region: region);
const Rect.fromLTRB(0, 0, 100, 100), });
);
test('draw growing picture across frames clipped', () async {
_drawTestPicture(builder, 100, true); final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
builder.pop(); builder.pushClipRect(
const Rect.fromLTRB(0, 0, 100, 100),
html.Element elm1 = builder.build().webOnlyRootElement; );
html.document.body.append(elm1);
_drawTestPicture(builder, 100, true);
// Now draw picture again but at larger size. builder.pop();
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
builder2.pushClipRect( html.Element elm1 = builder
const Rect.fromLTRB(0, 0, 100, 100), .build()
); .webOnlyRootElement;
_drawTestPicture(builder2, 20, true); html.document.body.append(elm1);
builder2.pop();
// Now draw picture again but at larger size.
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
builder2.pushClipRect(
const Rect.fromLTRB(0, 0, 100, 100),
);
_drawTestPicture(builder2, 20, true);
builder2.pop();
elm1.remove();
html.document.body.append(builder2
.build()
.webOnlyRootElement);
await matchGoldenFile('canvas_draw_picture_acrossframes_clipped.png',
region: region);
});
test('PictureInPicture', () async {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
final Picture greenRectPicture = _drawGreenRectIntoPicture();
final EnginePictureRecorder recorder = PictureRecorder();
final RecordingCanvas canvas =
recorder.beginRecording(const Rect.fromLTRB(0, 0, 100, 100));
canvas.drawPicture(greenRectPicture);
builder.addPicture(Offset(10, 10), recorder.endRecording());
elm1.remove(); html.Element elm1 = builder
html.document.body.append(builder2.build().webOnlyRootElement); .build()
.webOnlyRootElement;
html.document.body.append(elm1);
await matchGoldenFile('canvas_draw_picture_acrossframes_clipped.png', await matchGoldenFile('canvas_draw_picture_in_picture_rect.png',
region: region); region: region);
});
}); });
} }
...@@ -110,6 +139,15 @@ void _drawTestPicture(SceneBuilder builder, double targetSize, bool clipped) { ...@@ -110,6 +139,15 @@ void _drawTestPicture(SceneBuilder builder, double targetSize, bool clipped) {
); );
} }
Picture _drawGreenRectIntoPicture() {
final EnginePictureRecorder recorder = PictureRecorder();
final RecordingCanvas canvas =
recorder.beginRecording(const Rect.fromLTRB(0, 0, 100, 100));
canvas.drawRect(Rect.fromLTWH(20, 20, 50, 50),
Paint()..color = const Color(0xFF00FF00));
return recorder.endRecording();
}
typedef PaintCallback = void Function(RecordingCanvas canvas); typedef PaintCallback = void Function(RecordingCanvas canvas);
const String _base64Encoded20x20TestImage = const String _base64Encoded20x20TestImage =
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册