saveLayer method Null safety

void saveLayer (
  1. Rect? bounds,
  2. Paint paint
)

Saves a copy of the current transform and clip on the save stack, and then creates a new group which subsequent calls will become a part of. When the save stack is later popped, the group will be flattened into a layer and have the given paint's Paint.colorFilter and Paint.blendMode applied.

This lets you create composite effects, for example making a group of drawing commands semi-transparent. Without using saveLayer, each part of the group would be painted individually, so where they overlap would be darker than where they do not. By using saveLayer to group them together, they can be drawn with an opaque color at first, and then the entire group can be made transparent using the saveLayer's paint.

Call restore to pop the save stack and apply the paint to the group.

Using saveLayer with clips

When a rectangular clip operation (from clipRect) is not axis-aligned with the raster buffer, or when the clip operation is not rectilinear (e.g. because it is a rounded rectangle clip created by clipRRect or an arbitrarily complicated path clip created by clipPath), the edge of the clip needs to be anti-aliased.

If two draw calls overlap at the edge of such a clipped region, without using saveLayer, the first drawing will be anti-aliased with the background first, and then the second will be anti-aliased with the result of blending the first drawing and the background. On the other hand, if saveLayer is used immediately after establishing the clip, the second drawing will cover the first in the layer, and thus the second alone will be anti-aliased with the background when the layer is clipped and composited (when restore is called).

For example, this CustomPainter.paint method paints a clean white rounded rectangle:

void paint(Canvas canvas, Size size) {
  Rect rect = Offset.zero & size;
  canvas.save();
  canvas.clipRRect(new RRect.fromRectXY(rect, 100.0, 100.0));
  canvas.saveLayer(rect, Paint());
  canvas.drawPaint(new Paint()..color = Colors.red);
  canvas.drawPaint(new Paint()..color = Colors.white);
  canvas.restore();
  canvas.restore();
}

On the other hand, this one renders a red outline, the result of the red paint being anti-aliased with the background at the clip edge, then the white paint being similarly anti-aliased with the background including the clipped red paint:

void paint(Canvas canvas, Size size) {
  // (this example renders poorly, prefer the example above)
  Rect rect = Offset.zero & size;
  canvas.save();
  canvas.clipRRect(new RRect.fromRectXY(rect, 100.0, 100.0));
  canvas.drawPaint(new Paint()..color = Colors.red);
  canvas.drawPaint(new Paint()..color = Colors.white);
  canvas.restore();
}

This point is moot if the clip only clips one draw operation. For example, the following paint method paints a pair of clean white rounded rectangles, even though the clips are not done on a separate layer:

void paint(Canvas canvas, Size size) {
  canvas.save();
  canvas.clipRRect(new RRect.fromRectXY(Offset.zero & (size / 2.0), 50.0, 50.0));
  canvas.drawPaint(new Paint()..color = Colors.white);
  canvas.restore();
  canvas.save();
  canvas.clipRRect(new RRect.fromRectXY(size.center(Offset.zero) & (size / 2.0), 50.0, 50.0));
  canvas.drawPaint(new Paint()..color = Colors.white);
  canvas.restore();
}

(Incidentally, rather than using clipRRect and drawPaint to draw rounded rectangles like this, prefer the drawRRect method. These examples are using drawPaint as a proxy for "complicated draw operations that will get clipped", to illustrate the point.)

Performance considerations

Generally speaking, saveLayer is relatively expensive.

There are a several different hardware architectures for GPUs (graphics processing units, the hardware that handles graphics), but most of them involve batching commands and reordering them for performance. When layers are used, they cause the rendering pipeline to have to switch render target (from one layer to another). Render target switches can flush the GPU's command buffer, which typically means that optimizations that one could get with larger batching are lost. Render target switches also generate a lot of memory churn because the GPU needs to copy out the current frame buffer contents from the part of memory that's optimized for writing, and then needs to copy it back in once the previous render target (layer) is restored.

See also:

Implementation

void saveLayer(Rect? bounds, Paint paint) {
  assert(paint != null); // ignore: unnecessary_null_comparison
  if (bounds == null) {
    _saveLayerWithoutBounds(paint._objects, paint._data);
  } else {
    assert(_rectIsValid(bounds));
    _saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
               paint._objects, paint._data);
  }
}