• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5part of engine;
6
7/// A layer to be composed into a scene.
8///
9/// A layer is the lowest-level rendering primitive. It represents an atomic
10/// painting command.
11abstract class Layer implements ui.EngineLayer {
12  /// The layer that contains us as a child.
13  ContainerLayer parent;
14
15  /// An estimated rectangle that this layer will draw into.
16  ui.Rect paintBounds = ui.Rect.zero;
17
18  /// Whether or not this layer actually needs to be painted in the scene.
19  bool get needsPainting => !paintBounds.isEmpty;
20
21  /// Pre-process this layer before painting.
22  ///
23  /// In this step, we compute the estimated [paintBounds] as well as
24  /// apply heuristics to prepare the render cache for pictures that
25  /// should be cached.
26  void preroll(PrerollContext prerollContext, Matrix4 matrix);
27
28  /// Paint this layer into the scene.
29  void paint(PaintContext paintContext);
30}
31
32/// A context shared by all layers during the preroll pass.
33class PrerollContext {
34  /// A raster cache. Used to register candidates for caching.
35  final RasterCache rasterCache;
36
37  PrerollContext(this.rasterCache);
38}
39
40/// A context shared by all layers during the paint pass.
41class PaintContext {
42  /// The canvas to paint to.
43  final SkCanvas canvas;
44
45  /// A raster cache potentially containing pre-rendered pictures.
46  final RasterCache rasterCache;
47
48  PaintContext(this.canvas, this.rasterCache);
49}
50
51/// A layer that contains child layers.
52abstract class ContainerLayer extends Layer {
53  final List<Layer> _layers = <Layer>[];
54
55  /// Register [child] as a child of this layer.
56  void add(Layer child) {
57    child.parent = this;
58    _layers.add(child);
59  }
60
61  @override
62  void preroll(PrerollContext prerollContext, Matrix4 matrix) {
63    paintBounds = prerollChildren(prerollContext, matrix);
64  }
65
66  /// Run [preroll] on all of the child layers.
67  ///
68  /// Returns a [Rect] that covers the paint bounds of all of the child layers.
69  /// If all of the child layers have empty paint bounds, then the returned
70  /// [Rect] is empty.
71  ui.Rect prerollChildren(PrerollContext context, Matrix4 childMatrix) {
72    ui.Rect childPaintBounds = ui.Rect.zero;
73    for (Layer layer in _layers) {
74      layer.preroll(context, childMatrix);
75      if (childPaintBounds.isEmpty) {
76        childPaintBounds = layer.paintBounds;
77      } else if (!layer.paintBounds.isEmpty) {
78        childPaintBounds = childPaintBounds.expandToInclude(layer.paintBounds);
79      }
80    }
81    return childPaintBounds;
82  }
83
84  /// Calls [paint] on all child layers that need painting.
85  void paintChildren(PaintContext context) {
86    assert(needsPainting);
87
88    for (Layer layer in _layers) {
89      if (layer.needsPainting) {
90        layer.paint(context);
91      }
92    }
93  }
94}
95
96/// A layer that clips its child layers by a given [Path].
97class ClipPathLayer extends ContainerLayer {
98  /// The path used to clip child layers.
99  final ui.Path _clipPath;
100
101  ClipPathLayer(this._clipPath);
102
103  @override
104  void preroll(PrerollContext prerollContext, Matrix4 matrix) {
105    final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix);
106    final ui.Rect clipBounds = _clipPath.getBounds();
107    if (childPaintBounds.overlaps(clipBounds)) {
108      paintBounds = childPaintBounds.intersect(clipBounds);
109    }
110  }
111
112  @override
113  void paint(PaintContext paintContext) {
114    assert(needsPainting);
115
116    paintContext.canvas.save();
117    paintContext.canvas.clipPath(_clipPath);
118    paintChildren(paintContext);
119    paintContext.canvas.restore();
120  }
121}
122
123/// A layer that clips its child layers by a given [Rect].
124class ClipRectLayer extends ContainerLayer {
125  /// The rectangle used to clip child layers.
126  final ui.Rect _clipRect;
127
128  ClipRectLayer(this._clipRect);
129
130  @override
131  void preroll(PrerollContext prerollContext, Matrix4 matrix) {
132    final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix);
133    if (childPaintBounds.overlaps(_clipRect)) {
134      paintBounds = childPaintBounds.intersect(_clipRect);
135    }
136  }
137
138  @override
139  void paint(PaintContext paintContext) {
140    assert(needsPainting);
141
142    paintContext.canvas.save();
143    paintContext.canvas.clipRect(_clipRect);
144    paintChildren(paintContext);
145    paintContext.canvas.restore();
146  }
147}
148
149/// A layer that clips its child layers by a given [RRect].
150class ClipRRectLayer extends ContainerLayer {
151  /// The rounded rectangle used to clip child layers.
152  final ui.RRect _clipRRect;
153
154  ClipRRectLayer(this._clipRRect);
155
156  @override
157  void preroll(PrerollContext prerollContext, Matrix4 matrix) {
158    final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix);
159    if (childPaintBounds.overlaps(_clipRRect.outerRect)) {
160      paintBounds = childPaintBounds.intersect(_clipRRect.outerRect);
161    }
162  }
163
164  @override
165  void paint(PaintContext paintContext) {
166    assert(needsPainting);
167
168    paintContext.canvas.save();
169    paintContext.canvas.clipRRect(_clipRRect);
170    paintChildren(paintContext);
171    paintContext.canvas.restore();
172  }
173}
174
175/// A layer that transforms its child layers by the given transform matrix.
176class TransformLayer extends ContainerLayer
177    implements ui.OffsetEngineLayer, ui.TransformEngineLayer {
178  /// The matrix with which to transform the child layers.
179  final Matrix4 _transform;
180
181  TransformLayer(this._transform);
182
183  @override
184  void preroll(PrerollContext prerollContext, Matrix4 matrix) {
185    final Matrix4 childMatrix = matrix * _transform;
186    final ui.Rect childPaintBounds =
187        prerollChildren(prerollContext, childMatrix);
188    paintBounds = _transformRect(_transform, childPaintBounds);
189  }
190
191  /// Applies the given matrix as a perspective transform to the given point.
192  ///
193  /// This function assumes the given point has a z-coordinate of 0.0. The
194  /// z-coordinate of the result is ignored.
195  static ui.Offset _transformPoint(Matrix4 transform, ui.Offset point) {
196    final Vector3 position3 = Vector3(point.dx, point.dy, 0.0);
197    final Vector3 transformed3 = transform.perspectiveTransform(position3);
198    return ui.Offset(transformed3.x, transformed3.y);
199  }
200
201  /// Returns a rect that bounds the result of applying the given matrix as a
202  /// perspective transform to the given rect.
203  ///
204  /// This function assumes the given rect is in the plane with z equals 0.0.
205  /// The transformed rect is then projected back into the plane with z equals
206  /// 0.0 before computing its bounding rect.
207  static ui.Rect _transformRect(Matrix4 transform, ui.Rect rect) {
208    final ui.Offset point1 = _transformPoint(transform, rect.topLeft);
209    final ui.Offset point2 = _transformPoint(transform, rect.topRight);
210    final ui.Offset point3 = _transformPoint(transform, rect.bottomLeft);
211    final ui.Offset point4 = _transformPoint(transform, rect.bottomRight);
212    return ui.Rect.fromLTRB(
213        _min4(point1.dx, point2.dx, point3.dx, point4.dx),
214        _min4(point1.dy, point2.dy, point3.dy, point4.dy),
215        _max4(point1.dx, point2.dx, point3.dx, point4.dx),
216        _max4(point1.dy, point2.dy, point3.dy, point4.dy));
217  }
218
219  static double _min4(double a, double b, double c, double d) {
220    return math.min(a, math.min(b, math.min(c, d)));
221  }
222
223  static double _max4(double a, double b, double c, double d) {
224    return math.max(a, math.max(b, math.max(c, d)));
225  }
226
227  @override
228  void paint(PaintContext paintContext) {
229    assert(needsPainting);
230
231    paintContext.canvas.save();
232    paintContext.canvas.transform(_transform.storage);
233    paintChildren(paintContext);
234    paintContext.canvas.restore();
235  }
236}
237
238/// A layer containing a [Picture].
239class PictureLayer extends Layer {
240  /// The picture to paint into the canvas.
241  final ui.Picture picture;
242
243  /// The offset at which to paint the picture.
244  final ui.Offset offset;
245
246  /// A hint to the compositor about whether this picture is complex.
247  final bool isComplex;
248
249  /// A hint to the compositor that this picture is likely to change.
250  final bool willChange;
251
252  PictureLayer(this.picture, this.offset, this.isComplex, this.willChange);
253
254  @override
255  void preroll(PrerollContext prerollContext, Matrix4 matrix) {
256    final RasterCache cache = prerollContext.rasterCache;
257    if (cache != null) {
258      final Matrix4 translateMatrix = Matrix4.identity()
259        ..setTranslationRaw(offset.dx, offset.dy, 0);
260      final Matrix4 cacheMatrix = translateMatrix * matrix;
261      cache.prepare(picture, cacheMatrix, isComplex, willChange);
262    }
263
264    paintBounds = picture.cullRect.shift(offset);
265  }
266
267  @override
268  void paint(PaintContext paintContext) {
269    assert(picture != null);
270    assert(needsPainting);
271
272    paintContext.canvas.save();
273    paintContext.canvas.translate(offset.dx, offset.dy);
274
275    if (paintContext.rasterCache != null) {
276      final Matrix4 cacheMatrix = paintContext.canvas.currentTransform;
277      final RasterCacheResult result =
278          paintContext.rasterCache.get(picture, cacheMatrix);
279      if (result.isValid) {
280        result.draw(paintContext.canvas);
281        return;
282      }
283    }
284    paintContext.canvas.drawPicture(picture);
285    paintContext.canvas.restore();
286  }
287}
288
289/// A layer representing a physical shape.
290///
291/// The shape clips its children to a given [Path], and casts a shadow based
292/// on the given elevation.
293class PhysicalShapeLayer extends ContainerLayer
294    implements ui.PhysicalShapeEngineLayer {
295  final double _elevation;
296  final ui.Color _color;
297  final ui.Color _shadowColor;
298  final ui.Path _path;
299  final ui.Clip _clipBehavior;
300
301  PhysicalShapeLayer(
302    this._elevation,
303    this._color,
304    this._shadowColor,
305    this._path,
306    this._clipBehavior,
307  );
308
309  @override
310  void preroll(PrerollContext prerollContext, Matrix4 matrix) {
311    prerollChildren(prerollContext, matrix);
312    paintBounds =
313        ElevationShadow.computeShadowRect(_path.getBounds(), _elevation);
314  }
315
316  @override
317  void paint(PaintContext paintContext) {
318    assert(needsPainting);
319
320    if (_elevation != 0) {
321      drawShadow(paintContext.canvas, _path, _shadowColor, _elevation,
322          _color.alpha != 0xff);
323    }
324
325    final ui.Paint paint = ui.Paint()..color = _color;
326    if (_clipBehavior != ui.Clip.antiAliasWithSaveLayer) {
327      paintContext.canvas.drawPath(_path, paint);
328    }
329
330    final int saveCount = paintContext.canvas.save();
331    switch (_clipBehavior) {
332      case ui.Clip.hardEdge:
333        paintContext.canvas.clipPath(_path);
334        break;
335      case ui.Clip.antiAlias:
336        // TODO(het): This is supposed to be different from Clip.hardEdge in
337        // that it anti-aliases the clip. The canvas clipPath() method
338        // should support this.
339        paintContext.canvas.clipPath(_path);
340        break;
341      case ui.Clip.antiAliasWithSaveLayer:
342        paintContext.canvas.clipPath(_path);
343        paintContext.canvas.saveLayer(paintBounds, null);
344        break;
345      case ui.Clip.none:
346        break;
347    }
348
349    if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
350      // If we want to avoid the bleeding edge artifact
351      // (https://github.com/flutter/flutter/issues/18057#issue-328003931)
352      // using saveLayer, we have to call drawPaint instead of drawPath as
353      // anti-aliased drawPath will always have such artifacts.
354      paintContext.canvas.drawPaint(paint);
355    }
356
357    paintChildren(paintContext);
358
359    paintContext.canvas.restoreToCount(saveCount);
360  }
361
362  /// Draws a shadow on the given [canvas] for the given [path].
363  ///
364  /// The blur of the shadow is decided by the [elevation], and the
365  /// shadow is painted with the given [color].
366  static void drawShadow(SkCanvas canvas, ui.Path path, ui.Color color,
367      double elevation, bool transparentOccluder) {
368    canvas.drawShadow(path, color, elevation, transparentOccluder);
369  }
370}
371