pub struct Canvas { /* private fields */ }Expand description
Records drawing commands for one frame.
Implementations§
Source§impl Canvas
impl Canvas
Sourcepub fn with_samples(self, samples: u32) -> Canvas
pub fn with_samples(self, samples: u32) -> Canvas
Sample count to use when anything is antialiased.
pub fn extent(&self) -> Extent2D
Sourcepub fn clear(&mut self, color: Color) -> &mut Canvas
pub fn clear(&mut self, color: Color) -> &mut Canvas
Fill the whole target before drawing anything else.
Required, not merely usual, if the frame itself draws an antialiased shape that is not analytic. Such a shape renders through a multisample buffer, and there is no way to seed that buffer with what the target already held – so a multisampled pass has to clear, and a canvas with no background gives its root pass nothing to clear to. Drawing one is refused rather than quietly discarding whatever was on the surface.
The frame itself, and not the picture: a sample count describes one pass. The same shape drawn inside a layer needs the layer’s pass multisampled and leaves the frame alone, since what the frame does with a finished layer is draw one image quad. So a shadow, which is a layer underneath, needs no background at all.
Paint::with_anti_alias is the other end of the choice.
Sourcepub fn transform(&self) -> Transform2D
pub fn transform(&self) -> Transform2D
Current transform, mapping user coordinates to device pixels.
Sourcepub fn clip(&self) -> Option<Scissor>
pub fn clip(&self) -> Option<Scissor>
The region drawing is currently confined to, in device pixels.
None is the whole target.
Sourcepub fn destination_clip_bounds(&self) -> Rect
pub fn destination_clip_bounds(&self) -> Rect
The region drawing may still reach, in device pixels.
Conservative: it is a rectangle around the clip rather than the clip itself, so a path clip reports its bounding box and a rotated rectangle reports the axis-aligned box around it. That is what makes it useful for the thing it is for – deciding not to draw something – since a caller may skip anything outside it and must not assume everything inside it is visible.
Sourcepub fn local_clip_bounds(&self) -> Rect
pub fn local_clip_bounds(&self) -> Rect
The same region in the coordinates the caller is currently drawing in.
The device bounds carried back through the transform, which under a rotation gives the box around the rotated box and so grows a little each time. Conservative in the same direction as everything else here: too large is a missed optimization, too small is a missing shape.
A transform that cannot be inverted has collapsed the plane, and nothing drawn through it reaches anything; the empty rectangle says so.
Sourcepub fn clip_rect(&mut self, rect: Rect) -> Result<&mut Canvas, Error>
pub fn clip_rect(&mut self, rect: Rect) -> Result<&mut Canvas, Error>
Narrow the clip to a rectangle in user space.
Intersects with the clip already in force rather than replacing it, which is what makes the clip stack compose: a subtree can only ever narrow what its parent allowed. The rectangle goes through the current transform, so it moves with the coordinate system the caller set up.
§Rotation
Returns Error::Unsupported when the current transform rotates or
skews by anything other than a quarter turn, because the result is then
a rotated quadrilateral and this clip is a rectangle. Refusing is the
point: the tempting alternative is to use the rotated shape’s bounding
box, which admits pixels the caller asked to remove, and produces a
picture that is wrong in a way that looks like a rendering bug rather
than like a clip that was never applied. Arbitrary clip shapes need a
stencil pass, which is a separate piece of machinery.
Sourcepub fn clip_out_rect(&mut self, rect: Rect) -> Result<&mut Canvas, Error>
pub fn clip_out_rect(&mut self, rect: Rect) -> Result<&mut Canvas, Error>
Narrow the clip to everything outside a rectangle in user space.
dart:ui spells this clipRect with ClipOp.difference, and it is the
only clip that operation applies to there – clipPath and clipRRect
intersect and take no operation.
Never a scissor, whatever the transform: the complement of a rectangle
is not one, and a scissor is. So this goes through the stencil like an
arbitrary shape, and the shape it narrows by is the target with the
rectangle taken out of it – two contours filled by the even-odd rule,
which is what makes the second a hole in the first rather than a second
region drawn over it. draw_drrect builds a ring the same way.
Both contours are built in device coordinates and filled through the identity, rather than in user space through the transform. The reason is the target: it is a device rectangle, and expressing it in user space would mean inverting a transform that may not be invertible. The rectangle’s own corners go through the transform here instead, so a rotation gives the quadrilateral it should rather than a box around one.
Sourcepub fn clip_path(&mut self, path: &Path) -> Result<&mut Canvas, Error>
pub fn clip_path(&mut self, path: &Path) -> Result<&mut Canvas, Error>
Narrow the clip to an arbitrary path in user space.
Intersects with the clip already in force, like Self::clip_rect, and
like it the path travels through the current transform. The shape is
recorded into the batch as a draw that writes only the stencil, so it
costs one draw where a rectangular clip costs none.
The path is filled by the same tessellator that fills a drawn shape, so it obeys the same fill rule and produces the same edges. That matters for more than consistency: the tessellation is a set of non-overlapping triangles, which is what lets the stencil step forward by one rather than needing the parity trick an overlapping fan would.
Sourcepub fn save(&mut self) -> &mut Canvas
pub fn save(&mut self) -> &mut Canvas
Save the transform and clip so a later restore can return to them.
Sourcepub fn save_layer(&mut self, layer: Layer) -> &mut Canvas
pub fn save_layer(&mut self, layer: Layer) -> &mut Canvas
Begin drawing into a layer of its own, composited back on restore.
Everything recorded until the matching restore goes into a separate target and arrives as one image, which is what makes an alpha here apply to the group rather than to each shape in it.
The layer starts unclipped and at the identity for clipping purposes, while keeping the current transform. It does not need to inherit the clip because the draw that composites it is subject to it: content the clip excludes is discarded once rather than prevented from being drawn, which costs some work in the layer and keeps a stencil clip from having to be rebuilt in a second target.
Sourcepub fn save_layer_bounds(&mut self, layer: Layer, bounds: Rect) -> &mut Canvas
pub fn save_layer_bounds(&mut self, layer: Layer, bounds: Rect) -> &mut Canvas
Open a layer that only needs to cover bounds.
bounds is in the current user space, and is the caller’s promise that
nothing drawn in the layer matters outside it. The layer gets a target
the size of that region rather than the size of the frame, which is the
whole reason to state it: a layer over a tenth of the frame then costs a
tenth of the memory and a tenth of the fill, and a frame that opens
several stops being dominated by full-frame allocations it does not use.
The promise is enforced by construction rather than trusted. Content outside the region is clipped away by the target’s own edges, so a caller who understates the bounds sees the drawing cut off — visible and attributable, rather than reading stale memory.
The region is taken to device pixels and rounded outward to whole ones, so a fractional bound never costs coverage at the edge, and is narrowed to what the enclosing target can show, since a layer larger than that renders pixels nothing can sample. A region that comes out empty falls back to a full-size layer, that being a case where a smaller target would be a guess and guessing wrong loses drawing.
A layer that filters its backdrop is the exception to all of this: for
it the bounds are not an optimization but the region filtered, so
omitting them filters the whole target rather than merely allocating
more of one. See Layer::backdrop_blur.
Under a rotation the region becomes a quadrilateral, and the target is
the box around it. That covers more than the caller promised, which is
the safe direction: a target is an allocation rather than a clip the
caller can observe, so over-covering costs a little memory where
under-covering would lose drawing. This is the opposite of what
Self::clip_rect does with the same box, and for the same reason —
there the box would admit pixels the caller asked to remove.
Sourcepub fn save_layer_filtered(
&mut self,
layer: Layer,
bounds: Option<Rect>,
filter: &ImageFilter,
) -> Result<&mut Canvas, Error>
pub fn save_layer_filtered( &mut self, layer: Layer, bounds: Option<Rect>, filter: &ImageFilter, ) -> Result<&mut Canvas, Error>
A layer filtered as a whole by any image filter.
Layer’s own fields carry the four kinds that fit in a Copy struct
– a blur, a morphology, a matrix and a color filter – and apply them
in one fixed order. This takes an ImageFilter, which adds the two
they cannot say: a caller’s fragment program, and a composition in
whichever order the caller wrote it.
The same operation dart:ui spells as an imageFilter on the paint
handed to saveLayer, and the group counterpart of what
Paint::with_image_filter already does for one draw.
Refuses a matrix, which is a redirection rather than an absence: a
group’s finished image is moved by Layer::with_matrix, which states
the same thing and is what peel turns this filter into everywhere
else. Refused here rather than at restore, because a restore has no
result to fail into and by then the caller has drawn into the layer.
A backdrop’s matrix is not refused; see backdrop_pass. The two are
different questions – a group’s own image is composited by machinery
that already takes a matrix, and a backdrop’s is seeded by a draw that
now does.
Sourcepub fn save_layer_backdrop(
&mut self,
layer: Layer,
bounds: Option<Rect>,
backdrop: &ImageFilter,
) -> Result<&mut Canvas, Error>
pub fn save_layer_backdrop( &mut self, layer: Layer, bounds: Option<Rect>, backdrop: &ImageFilter, ) -> Result<&mut Canvas, Error>
A layer over a backdrop filtered by any image filter.
Layer::backdrop_blur is the same operation with the one filter a
Copy layer can hold, and is the spelling to reach for when a blur is
what is wanted. This takes the rest: a color filter, a morphology, a
caller’s fragment program, or a composition of them – which is what
SceneBuilder.pushBackdropFilter accepts and what upstream’s
SaveLayer takes a DlImageFilter for.
The bounds are the filtered region rather than an optimization, for the
reason Layer::backdrop_blur gives: a frosted panel is a bounded
layer, and the same layer unbounded filters the whole frame. Both are
meaningful and they are different pictures.
Refuses a matrix, which is the one kind that moves the image rather than
recomputing it in place. See filter_passes.
Sourcepub fn layer_depth(&self) -> usize
pub fn layer_depth(&self) -> usize
How many layers are open, for a caller checking its own balance.
Sourcepub fn restore(&mut self) -> &mut Canvas
pub fn restore(&mut self) -> &mut Canvas
Return to the most recently saved transform and clip.
Restoring without a matching save leaves both alone rather than panicking: an unbalanced pair is a caller bug, but taking down a frame loop for it is worse than continuing with what is already correct.
Sourcepub fn save_depth(&self) -> usize
pub fn save_depth(&self) -> usize
How many saves are outstanding, for a caller checking its own balance.
pub fn translate(&mut self, x: f32, y: f32) -> &mut Canvas
pub fn scale(&mut self, x: f32, y: f32) -> &mut Canvas
Sourcepub fn concat(&mut self, transform: impl Into<Transform2D>) -> &mut Canvas
pub fn concat(&mut self, transform: impl Into<Transform2D>) -> &mut Canvas
Apply an arbitrary transform on top of the current one.
Takes anything that is a transform of the plane, which is an Affine2
for almost every caller and a Transform2D for one that wants
perspective. Widened rather than replaced: nobody should have to build a
three-by-three to move something ten pixels to the right.
Sourcepub fn concat_4x4(&mut self, matrix: &[f32; 16]) -> &mut Canvas
pub fn concat_4x4(&mut self, matrix: &[f32; 16]) -> &mut Canvas
Apply a dart:ui four-by-four, which is column-major and sixteen long.
The entry point for perspective, and the one dart:ui states: its
Canvas.transform takes a matrix of this shape. Named apart from
Self::transform, which reports the current one and cannot share a
name with a method that changes it.
The reduction to a three-by-three is exact rather than a narrowing.
Everything drawn here lies on the plane where z is zero, and a
four-by-four applied to such a point never reads its z column; the z
row yields a depth with nothing to do here. What is left – rows and
columns zero, one and three – is the whole of what the matrix means on
the plane. A caller whose matrix depends on z is getting the part of
it that acts on what they are drawing, which is all of it.
pub fn draw_path( &mut self, path: &Path, paint: &Paint, ) -> Result<&mut Canvas, Error>
Sourcepub fn draw_rect(
&mut self,
rect: Rect,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_rect( &mut self, rect: Rect, paint: &Paint, ) -> Result<&mut Canvas, Error>
Fill or stroke a rectangle.
An antialiased solid fill takes the same distance field a rounded rectangle does, with no corner to round. The vertex count is the same either way – a rectangle is four vertices whichever route it takes – so what this buys is the edge: the pass does not have to multisample for a shape that computes its own coverage, which is four times the fill and four times the bandwidth saved on a frame made mostly of rectangles.
Sourcepub fn draw_rrect(
&mut self,
rect: Rect,
radius: f32,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_rrect( &mut self, rect: Rect, radius: f32, paint: &Paint, ) -> Result<&mut Canvas, Error>
Fill or stroke a rectangle with rounded corners.
The shape an interface is mostly made of, and the reason it is here rather than left to the caller: building it from arcs by hand is a dozen lines that have to get the corner tangents right, and getting them slightly wrong shows as a corner that is subtly not round.
Tessellated like any other path. The analytic coverage that would let a rounded rectangle skip tessellation entirely is not implemented, and is where the interesting speed is for a real interface – this is the shape that would benefit most from it.
Sourcepub fn draw_rrect_with_radii(
&mut self,
rect: Rect,
radii: [[f32; 2]; 4],
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_rrect_with_radii( &mut self, rect: Rect, radii: [[f32; 2]; 4], paint: &Paint, ) -> Result<&mut Canvas, Error>
A rounded rectangle whose corners need not match.
The eight numbers dart:ui’s RRect carries, in the order upstream’s
RoundingRadii carries them: top-left, top-right, bottom-left,
bottom-right, each an x radius and a y radius. Radii that overrun the
side they share are fitted by dart:ui’s rule – see
Rect::to_rounded_path_with_radii.
Tessellated, always. The fragment-evaluated route Self::draw_rrect
can take is a signed distance to a shape with one circular radius, and
eight numbers is a different function rather than that one with more
arguments – so a corner that differs from its neighbors costs the
tessellation a uniform one avoids. That is the whole of what this costs,
and it is why the uniform call is still the one to reach for: it is not
a worse spelling of this, it is the case that has a shader.
Sourcepub fn draw_rsuperellipse(
&mut self,
rect: Rect,
radii: [[f32; 2]; 4],
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_rsuperellipse( &mut self, rect: Rect, radii: [[f32; 2]; 4], paint: &Paint, ) -> Result<&mut Canvas, Error>
Draw Flutter’s rounded superellipse – the iOS-style squircle.
dart:ui’s drawRSuperellipse. The corners are not arcs of one curve:
each is a superellipse arc, a circular arc across the diagonal, and a
second superellipse arc, with the superellipse’s degree read from a
fitted table on the ratio of side to radius. See
impeller_geometry::superellipse for what that means and for what
checks the transcription of the table.
Tessellated rather than evaluated in the fragment stage. A rounded rectangle of a single radius takes the analytic route because there is a closed form to evaluate; this shape has none, which is the same reason it took so long to arrive.
Sourcepub fn clip_rsuperellipse(
&mut self,
rect: Rect,
radii: [[f32; 2]; 4],
) -> Result<&mut Canvas, Error>
pub fn clip_rsuperellipse( &mut self, rect: Rect, radii: [[f32; 2]; 4], ) -> Result<&mut Canvas, Error>
Clip to Flutter’s rounded superellipse.
dart:ui’s clipRSuperellipse, and the same shape
Canvas::draw_rsuperellipse draws.
Sourcepub fn draw_shadow(
&mut self,
path: &Path,
color: Color,
elevation: f32,
_transparent_occluder: bool,
) -> Result<&mut Canvas, Error>
pub fn draw_shadow( &mut self, path: &Path, color: Color, elevation: f32, _transparent_occluder: bool, ) -> Result<&mut Canvas, Error>
Fill or stroke a circle.
An antialiased solid fill goes through the same distance field a rounded
rectangle does, because it is one: a square whose corner radius is half
its side has no straight edge left, and the field reduces exactly to the
distance from the center less the radius. So a circle costs two
triangles and needs no shader of its own, where four cubics flattened to
The shadow an object at elevation casts, under one light.
The rule rather than the picture, which is why this is a call and not a blurred draw a caller assembles: the whole point of an elevation is that everything at the same height casts a consistent shadow, and consistency is what a rule spread across call sites loses first.
The model is a single light above and behind the viewer, which is what Impeller uses and what makes a raised object’s shadow fall downward on screen. Three things follow from the elevation and nothing else: the shadow is offset downward by it, blurred in proportion to it, and drawn at a quarter of the stated color’s alpha before that alpha is adjusted for the color’s own luminance.
elevation is in the same units the canvas draws in. Impeller scales
it by a device pixel ratio first, which is a framework concept rather
than a rendering one; a caller who has one should apply it here.
transparent_occluder is accepted and unused, which is what upstream
does with it – DlDispatcherBase::drawShadow takes the flag and its
drawing code never reads it. It stays on the signature because
dart:ui has it, and a caller porting a call should not have to find
out that one argument went missing.
Sourcepub fn draw_circle(
&mut self,
center: Vec2,
radius: f32,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_circle( &mut self, center: Vec2, radius: f32, paint: &Paint, ) -> Result<&mut Canvas, Error>
a tolerance cost vertices in proportion to how large it is drawn.
Sourcepub fn draw_oval(
&mut self,
bounds: Rect,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_oval( &mut self, bounds: Rect, paint: &Paint, ) -> Result<&mut Canvas, Error>
Fill or stroke an ellipse inscribed in bounds.
A circle where the bounds are square, and the shape a rounded rectangle cannot become: past half its shorter side a rounded rectangle stops changing, giving a stadium, where an ellipse keeps curving on both axes.
An antialiased solid fill is evaluated per fragment like the other shapes here. Everything else is tessellated from four cubics, which approximate an ellipse exactly as well as they approximate a circle – which is to say closely, and not exactly.
Sourcepub fn draw_glyphs(
&mut self,
glyphs: &[PositionedGlyph],
atlas: &Atlas,
atlas_slot: u32,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_glyphs( &mut self, glyphs: &[PositionedGlyph], atlas: &Atlas, atlas_slot: u32, paint: &Paint, ) -> Result<&mut Canvas, Error>
Draw a run of positioned glyphs from one atlas, as a single draw.
atlas_slot indexes the image table supplied at draw time, and each
glyph names where in that atlas its coverage sits. A run is one draw
however many glyphs it holds, because the coordinates travel on the
vertices rather than in the paint — which is the reason vertices carry
them at all, text being the highest draw-count content there is.
The paint’s color is the text color; its shader is otherwise ignored, since the atlas supplies coverage rather than color. Gradient-filled text needs the run drawn into a layer and the gradient drawn through it, which is a thing a caller can already build out of what is here.
Sourcepub fn draw_vertices(
&mut self,
mesh: &Vertices,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_vertices( &mut self, mesh: &Vertices, paint: &Paint, ) -> Result<&mut Canvas, Error>
Draw a mesh of triangles the caller supplied.
The one drawing call whose geometry does not come from tessellating a
shape, which is why it is also the one that can produce triangles this
renderer would never have made: overlapping, degenerate, wound either
way. None of that is checked beyond what Vertices checks at
construction, because a mesh is a caller saying what to draw rather
than what to draw around, and second-guessing it would defeat the
point.
Not antialiased. Coverage here comes from the rasterizer’s own rule rather than from a distance field or a tessellated feather, and the interior edges of a mesh are seams between triangles that must not be feathered at all – a mesh whose triangles each faded at their borders would show a lattice of its own construction.
Texture coordinates, where the mesh carries them, require an image paint: they say where in a texture each vertex reads, and a paint with no texture leaves them meaning nothing. The reverse is allowed – an image paint with no coordinates maps by position, the same as on any other shape.
Sourcepub fn draw_atlas(
&mut self,
sprites: &[Sprite],
sheet: Extent2D,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_atlas( &mut self, sprites: &[Sprite], sheet: Extent2D, paint: &Paint, ) -> Result<&mut Canvas, Error>
Draw many pieces of one sheet, each with its own transform, in one draw.
This is draw_vertices with the mesh built for you, and building it is
the part worth not getting wrong: each sprite is a quad running from
the origin to its source’s size, placed by its transform, with the
source rectangle divided by the sheet’s size to become the
coordinates its corners read. A caller assembling that themselves has
four chances per sprite to transpose an axis.
One draw for the whole batch: a hundred sprites from one sheet differ only in their vertices, so they have no reason to be a hundred draws. Anything that would split them – a different sheet, a different paint – is a second call.
A loop over Self::draw_vertices would end up as one draw too, since
a batch merges adjacent draws that differ in nothing. What this saves
is the per-sprite arithmetic rather than the draw calls.
sheet is the size of the uploaded texture in texels, which the paint
does not carry: a recording is built without touching a device and has
never seen how large the texture is.
Per-sprite colors, which dart:ui offers here, need a color per vertex
and this vertex format has none. A tint on the paint applies to the
whole batch.
Sourcepub fn draw_points(
&mut self,
mode: PointMode,
points: &[Vec2],
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_points( &mut self, mode: PointMode, points: &[Vec2], paint: &Paint, ) -> Result<&mut Canvas, Error>
Draw a run of points, joined as the mode says.
A point is a segment of no length, which is what decides how it looks: the stroke’s cap is the whole of the shape. A round cap gives a dot the width across, a square cap a square of that side, and a butt cap – which adds nothing to either end of a segment – gives nothing at all. That is not a special case being handled; it is the only reading of a zero-length segment that stays consistent with how every other stroke is drawn.
Takes the stroke’s width even from a paint asking to fill, since there is no interior to fill and a point with no width is not a shape.
Sourcepub fn draw_recording(
&mut self,
recording: &Recording,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_recording( &mut self, recording: &Recording, paint: &Paint, ) -> Result<&mut Canvas, Error>
Draw a finished recording into this one.
dart:ui calls this drawPicture, and it is a smaller thing here than
there, which is worth saying plainly. An SkPicture is a command list,
so replaying one under a new transform re-runs the commands and
re-tessellates at the new scale. A Recording is already tessellated:
its paths were flattened at a tolerance taken from the transform in force
when they were recorded, and its vertices are in clip space. Magnified,
it shows the polygon it was flattened to.
So this composes scenes at about the scale they were recorded at. It is not the reuse optimization the same call is elsewhere, and a caller who wants that should re-record.
What it costs is a layer, because that is what it is: the recording’s passes are taken as they are and its root becomes an image this canvas samples. Nothing is re-recorded and no geometry is touched, which is what makes it cheap and also what makes it unable to re-tessellate.
§Slots
A recording naming a caller’s image by index keeps that index. The two recordings are submitted with one image table, so an index means the same thing in both – a caller composing recordings that disagree about what image three is has to renumber before recording, which is a thing they can see and this call cannot.
Its layers and its baked gradients are renumbered, since those name positions in lists this recording is appending to.
Sourcepub fn draw_drrect(
&mut self,
outer: Rect,
outer_radius: f32,
inner: Rect,
inner_radius: f32,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_drrect( &mut self, outer: Rect, outer_radius: f32, inner: Rect, inner_radius: f32, paint: &Paint, ) -> Result<&mut Canvas, Error>
Fill the ring between two rounded rectangles.
One path of two contours filled by the even-odd rule, which is what makes the inner one a hole rather than a second ring drawn on top. A caller could assemble this, and the reason not to leave them to it is the rule: two contours wound the same way fill solid under the nonzero rule and hollow under even-odd, and which one a border needs is not something to rediscover per call site.
Sourcepub fn draw_drrect_with_radii(
&mut self,
outer: Rect,
outer_radii: [[f32; 2]; 4],
inner: Rect,
inner_radii: [[f32; 2]; 4],
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_drrect_with_radii( &mut self, outer: Rect, outer_radii: [[f32; 2]; 4], inner: Rect, inner_radii: [[f32; 2]; 4], paint: &Paint, ) -> Result<&mut Canvas, Error>
A ring between two rounded rectangles whose corners need not match.
Self::draw_drrect with the eight numbers each, and the same shape:
two contours filled even-odd, which is what makes the inner one a hole.
Each rectangle’s radii are fitted to it on its own, since a ring is two
rounded rectangles and not one shape with a thickness.
Sourcepub fn draw_paint(&mut self, paint: &Paint) -> Result<&mut Canvas, Error>
pub fn draw_paint(&mut self, paint: &Paint) -> Result<&mut Canvas, Error>
Fill everything the clip still admits.
dart:ui calls this drawPaint. What it fills is the clip rather than
the target, which is why it is a call and not a rectangle a caller
writes: the rectangle they would need is the clip’s bounds carried back
through the current transform, and getting that wrong is invisible
until a transform is in force.
The bounds are conservative, so this may cover more than the clip admits – and the clip then removes the excess, which is what makes a superset the safe direction to be wrong in.
Sourcepub fn draw_color(
&mut self,
color: Color,
blend: BlendMode,
) -> Result<&mut Canvas, Error>
pub fn draw_color( &mut self, color: Color, blend: BlendMode, ) -> Result<&mut Canvas, Error>
Fill everything the clip admits with one color.
dart:ui calls this drawColor. Distinct from Self::clear, which
replaces the whole target and ignores the clip: this is a draw, so it
blends and it obeys what is in force.
Sourcepub fn draw_image_nine(
&mut self,
slot: u32,
size: Extent2D,
center: Rect,
into: Rect,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_image_nine( &mut self, slot: u32, size: Extent2D, center: Rect, into: Rect, paint: &Paint, ) -> Result<&mut Canvas, Error>
Draw an image stretched by its middle, keeping its corners.
dart:ui calls this drawImageNine, and it is the shape every
resizable panel with a border is made of. center is the part of the
image, in texels, that may stretch; what surrounds it is divided into
eight pieces that stretch along one axis or neither.
A caller could write the nine draws. The reason not to leave them to it is the rule rather than the arithmetic: which pieces stretch in which direction is the whole of what a nine-patch means, and nine call sites are nine chances to stretch a corner.
The paint supplies the blend, the filters and whether the edges are antialiased; its shader is replaced, since each of the nine pieces needs its own. A caller wanting the whole thing faded should draw it into a layer, which is what fading a group means everywhere else here.
A center reaching an edge leaves pieces of no width or height, and those are skipped rather than drawn empty. A center outside the image entirely is refused, since there is no reading of it that leaves nine pieces.
Sourcepub fn draw_line(
&mut self,
from: Vec2,
to: Vec2,
paint: &Paint,
) -> Result<&mut Canvas, Error>
pub fn draw_line( &mut self, from: Vec2, to: Vec2, paint: &Paint, ) -> Result<&mut Canvas, Error>
A line, and for a hairline one snapped to a pixel.
The snap is the third part of upstream’s minimum-size rule and the one
that decides whether a hairline looks like one. A line one pixel wide
whose center sits on a pixel boundary covers half of each of the two rows
it straddles, so it draws gray and two pixels soft; the same line centered
in a row covers it and draws black and one pixel crisp. Upstream moves it:
LineGeometry::GetPositionBuffer carries the endpoints into device space,
drops the transform, and rounds the constant coordinate to a half with
RoundToHalf – which is a pixel’s middle.
Gated exactly as upstream gates it, on width_ == 0.f and
IsTranslationScaleOnly. A width the caller stated is a width to respect,
and under a rotation or a shear no axis-aligned line stays one.
Narrower than upstream in one way, which is deliberate: a paint wanting a layer keeps the ordinary path. A mask blur’s sigma is stated in user space and this draws in device space, so the two would disagree under a scale – and a snap is worth half a pixel, not a wrong blur.