pub struct Layer {
pub blur: Vec2,
pub alpha: f32,
pub blend: BlendMode,
pub matrix: Option<Transform2D>,
pub backdrop_blur: f32,
pub backdrop_id: Option<i64>,
pub morphology: Option<Morphology>,
pub color_filter: ColorFilter,
}Expand description
How a layer is composited back onto what was underneath it.
A separate type from Paint rather than a reuse of it, because only two
of a paint’s parts mean anything here — there is no shape to fill and no
geometry to stroke — and a caller handed a Paint would reasonably expect
its shader to matter.
Fields§
§blur: Vec2Blur the group before compositing it, with this standard deviation per axis in device pixels.
Zero on both for no blur, which is the default and costs nothing: the extra passes are only recorded where a caller asked for one, and an axis whose deviation is zero costs no pass of its own.
Two deviations rather than one because dart:ui’s ImageFilter.blur
states sigmaX and sigmaY separately, and because the blur is two
separable passes here regardless – so the second is a field rather
than a mechanism. Self::with_blur sets both.
On the group rather than on a paint, because a blur is a function of a finished image and a paint describes one shape. Blurring each shape and compositing the results is a different picture from blurring the composite, and the second is what a shadow or a frosted panel means.
alpha: f32Scales the whole layer on the way back. This is what makes group opacity differ from per-shape opacity: two overlapping half-transparent shapes in a layer show one blended edge, where the same shapes drawn directly show where they cross.
blend: BlendModeHow the finished layer meets what was underneath it.
matrix: Option<Transform2D>Transform the finished layer on the way back, in device pixels.
Distinct from transforming what goes into it, which the canvas’s own transform already does. This resamples the finished image: magnifying through it gives the layer’s own pixels enlarged, where drawing the same shapes under a larger transform would give them redrawn at the larger size. That difference is the whole of what a matrix image filter means – a caller wanting the sharp one already has the transform stack.
It resamples what the layer captured, and a layer captures no more
than its target holds. So a matrix moves an image and cannot recover
what fell outside the layer to begin with: a shape drawn past the edge
of the frame is gone before this runs, where upstream sizes the layer
through the filter and brings it back. docs/non-parity.md §12 has the
case and what fixing it would cost.
None composites the layer where it was drawn, which is what every
layer did before this existed.
backdrop_blur: f32Blur what is already on the target before the layer draws over it.
This is the other blur, and the difference is which image is filtered.
Self::blur softens the layer’s own content, which is what a shadow
is. This softens what is behind it and hands the result to the layer
as its starting content, which is what frosted glass is: a panel that
obscures rather than one that is itself indistinct.
Zero for none, and it costs nothing when zero. When it is not zero it costs a copy of the target and two blur passes over it, because reading what a pass is writing is not something this renderer can do – see the architecture note on cutting a pass.
§Bounds stop being an optimization
For every other layer, Canvas::save_layer_bounds only says where the
content is, and the picture is the same either way. For this one the
bounds are the region that gets filtered: a frosted panel is a bounded
layer, and the same layer without bounds blurs the whole frame and
composites the whole frame back. Both are meaningful and they are
different pictures, so state the bounds when you mean a panel.
backdrop_id: Option<i64>Which captured backdrop this layer filters, if it shares one.
dart:ui’s pushBackdropFilter takes a backdropId and says what it
means: “when the first backdrop filter with a given id is processed
during rasterization, the state of the backdrop is recorded and cached.
All subsequent backdrop filters with the same identifier will apply
their filter to the cached backdrop.” So it is a different picture, not
only a faster one – overlapping panels sharing an id each filter what
was behind all of them, rather than each filtering the one before it.
That is the case it exists for. A list of frosted rows over one background wants each row to show the background, and without the id the second row shows the first row’s blur blurred again. With it, the capture happens once and each row filters it: the picture the caller meant, and one pass cut instead of one per row.
An id keys a captured image and nothing more. A layer naming one from inside another layer still filters what the id captured rather than its own target’s content, which is upstream’s reading and is the point of the case it tests with each panel wrapped in a save layer of its own.
None captures afresh, which is what every layer did before this
existed and is right whenever the backdrops genuinely differ.
morphology: Option<Morphology>Spread or shrink the group before compositing it. See Morphology.
On the group for the same reason Self::blur is: a maximum over a
window is a function of a finished image. Dilating each shape and then
compositing is a different picture from dilating the composite wherever
two shapes overlap.
None for no morphology, which is the default and costs nothing.
color_filter: ColorFilterRecolor the finished group on its way back.
What dart:ui means by the colorFilter on the paint saveLayer
takes, and the same distinction group opacity has: filtering each shape
and compositing the results differs from filtering the composite
wherever two of them overlap, because a filter is not linear in general.
It is also the only way to filter what a caller’s own fragment program drew. A runtime effect is a whole pipeline rather than a material this renderer’s shader evaluates, so there is nowhere in it to apply a matrix – the filter has to act on the image the program produced.
Implementations§
Source§impl Layer
impl Layer
Sourcepub fn with_matrix(self, matrix: impl Into<Transform2D>) -> Layer
pub fn with_matrix(self, matrix: impl Into<Transform2D>) -> Layer
Transform the finished layer on the way back. See Self::matrix.
Takes an affine or a projective transform, the same as concat. A layer
moved by one of these is a finished image being placed, so perspective
here gives the image seen at an angle rather than the content redrawn at
one – which is the whole distinction this filter exists to make, and it
survives the widening unchanged.
pub fn opacity(alpha: f32) -> Layer
pub fn with_blend(self, blend: BlendMode) -> Layer
Sourcepub fn with_color_filter(self, filter: ColorFilter) -> Layer
pub fn with_color_filter(self, filter: ColorFilter) -> Layer
Recolor the finished group on its way back. See Self::color_filter.
Sourcepub fn with_morphology(self, morphology: Morphology) -> Layer
pub fn with_morphology(self, morphology: Morphology) -> Layer
Spread or shrink the finished group. See Morphology.
A morphology that would move nothing becomes None, on the same
reasoning as a blur of zero: a caller animating a radius down to nothing
should get the unfiltered thing at the end, not two passes that copy the
image twice to say so.
Sourcepub fn with_backdrop_blur(self, sigma: f32) -> Layer
pub fn with_backdrop_blur(self, sigma: f32) -> Layer
Blur the finished group, with sigma in device pixels.
Anything at or below zero, or not a number, means no blur rather than an error: a caller animating a shadow’s softness to nothing should get the sharp thing, not a refusal at the end of the animation. Blur the backdrop this layer is drawn over.
Ignored unless finite and positive, on the same terms as
Self::with_blur: a sigma that is not a length describes no blur.
Sourcepub fn with_backdrop_id(self, id: i64) -> Layer
pub fn with_backdrop_id(self, id: i64) -> Layer
Share one captured backdrop with every layer naming the same id.
See Self::backdrop_id for what is shared and why it changes the
picture rather than only the cost. The id is the caller’s, and means
nothing beyond identity: any two values that differ keep their
backdrops apart.
The id outlives the target it captured. A layer naming one from inside another layer is seeded with the captured image placed where that layer’s target sits within it, which is the same arithmetic a bounded layer is already seeded by.
pub fn with_blur(self, sigma: f32) -> Layer
Sourcepub fn with_blur_xy(self, sigma_x: f32, sigma_y: f32) -> Layer
pub fn with_blur_xy(self, sigma_x: f32, sigma_y: f32) -> Layer
Blur the finished group by a deviation per axis, in device pixels.
Two, because dart:ui’s ImageFilter.blur states sigmaX and
sigmaY separately. A blur here is two separable passes already, one
along each axis, so each simply uses its own – and a pass whose
deviation is zero is skipped rather than run, which is what keeps a
blur along one axis from resampling the other for nothing.