Skip to main content

impeller_core/
paint.rs

1//! How a shape is drawn.
2
3use crate::canvas::{Morphology, Rect};
4use crate::color::Color;
5use glam::Vec2;
6use impeller_geometry::dash::Dash;
7use impeller_geometry::stroke::StrokeStyle;
8use impeller_geometry::transform::{transformed_bounds, unbounded, Transform2D};
9use impeller_hal::{BlendMode, Extent2D, TileMode};
10use impeller_hal::{ColorFilter, Sampling};
11
12/// A color stop in a gradient.
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct GradientStop {
15    pub color: Color,
16    /// Position along the gradient, from zero to one.
17    pub offset: f32,
18}
19
20impl GradientStop {
21    pub fn new(color: Color, offset: f32) -> Self {
22        Self { color, offset }
23    }
24}
25
26/// What fills a shape.
27#[derive(Debug, Clone, PartialEq)]
28pub enum Shader {
29    Solid(Color),
30    /// A gradient along the line between two points **in user space**.
31    ///
32    /// The endpoints travel through the canvas transform with the geometry, so
33    /// a gradient rotates and scales with the shape it fills rather than
34    /// staying pinned to the screen.
35    LinearGradient {
36        start: Vec2,
37        end: Vec2,
38        stops: Vec<GradientStop>,
39        /// What fills the shape beyond the two endpoints.
40        ///
41        /// A gradient is defined by two points and a shape is rarely exactly
42        /// that long, so this is not an edge case: clamping holds the end
43        /// colors, repeating tiles the ramp, and decal leaves the outside
44        /// empty. Set with [`Paint::with_tile_mode`], the same call an image
45        /// uses.
46        tile: TileMode,
47    },
48    /// A gradient outward from a center **in user space**, reaching its last
49    /// stop at `radius`.
50    RadialGradient {
51        center: Vec2,
52        radius: f32,
53        stops: Vec<GradientStop>,
54        /// What fills the shape beyond `radius`. See [`Shader::LinearGradient`].
55        tile: TileMode,
56    },
57    /// A texture, mapped onto a rectangle **in user space**.
58    ///
59    /// The rectangle travels through the canvas transform with the geometry, so
60    /// an image rotates and scales with the shape it fills. It is the region
61    /// the image covers, not the region drawn: filling a circle with this paint
62    /// draws a circular piece of the image.
63    ///
64    /// `slot` indexes the table supplied when the recording is drawn. The
65    /// canvas names a slot rather than holding a texture because it records
66    /// without touching a device, and a backend texture is not something it can
67    /// name. Assigning slots is the caller's business for now; a registry that
68    /// did it for them is a separate piece of design.
69    Image {
70        slot: u32,
71        rect: Rect,
72        /// Scales the sampled color, for drawing an image translucently.
73        alpha: f32,
74        tile: TileMode,
75        /// Which part of the image to draw, from zero to one in each axis.
76        ///
77        /// The whole image by default. Set it with [`Paint::with_source`], or
78        /// with [`Paint::with_source_pixels`] if you would rather state it in
79        /// texels and hand over the size you uploaded.
80        source: Rect,
81        /// Multiplies the sampled color. White changes nothing.
82        tint: Color,
83        /// How to read between texels. See [`Paint::with_sampling`].
84        sampling: Sampling,
85    },
86    /// A gradient around a center **in user space**, running between two angles
87    /// in radians, measured counter-clockwise from the positive X axis.
88    SweepGradient {
89        center: Vec2,
90        start_angle: f32,
91        end_angle: f32,
92        stops: Vec<GradientStop>,
93        /// What fills the directions the arc does not cover.
94        ///
95        /// A sweep of a full turn covers every direction and this changes
96        /// nothing; it is a partial sweep that has an outside.
97        tile: TileMode,
98    },
99    /// A caller's own fragment program.
100    ///
101    /// The one shader here this renderer did not write. `program` names one
102    /// registered with a context, and `uniforms` are the floats it reads --
103    /// laid out as the program's own declaration expects, which is a contract
104    /// between the caller's shader and the caller's code and not something
105    /// this renderer can check.
106    ///
107    /// At most [`RUNTIME_FLOATS`] of them; the rest are dropped rather than
108    /// overflowing into a block that has no room for them.
109    ///
110    /// [`RUNTIME_FLOATS`]: impeller_hal::RUNTIME_FLOATS
111    RuntimeEffect {
112        program: u32,
113        uniforms: Vec<f32>,
114        /// A texture slot the program may sample, if it declares one.
115        ///
116        /// The same table an image paint names, so a caller supplies them in
117        /// the same array beside the recording, in the order the program
118        /// declares its bindings.
119        ///
120        /// A binding the program declares and this leaves empty reads a
121        /// one-pixel opaque white texture -- what a solid fill binds, and what
122        /// keeps a pipeline's declared binding satisfied. Beyond
123        /// [`MAX_EFFECT_TEXTURES`] a caller is asking for a second descriptor
124        /// set layout, which would be one layout per program; the ceiling is
125        /// what buys a single shared one.
126        ///
127        /// [`MAX_EFFECT_TEXTURES`]: impeller_hal::MAX_EFFECT_TEXTURES
128        images: Vec<u32>,
129    },
130    /// A gradient between two circles **in user space**, reaching its first
131    /// stop on the first circle and its last on the second.
132    ///
133    /// This is the general form: a radial gradient is the case where the first
134    /// circle is a point at the second's center, and the two-point form is what
135    /// `dart:ui` reaches by giving `Gradient.radial` a focal point. It is kept
136    /// separate from [`Shader::RadialGradient`] rather than replacing it,
137    /// because the radial case folds its radius into a matrix and solves no
138    /// quadratic at all, and paying for the general solve on every ordinary
139    /// gradient would be a poor trade on the hardware this targets.
140    ///
141    /// Where no circle in the family passes through a point, nothing is drawn
142    /// there -- which is most of the plane when one circle is far outside the
143    /// other. That is a property of the two circles rather than of the tile
144    /// mode, which acts only where the parameter exists.
145    ConicalGradient {
146        start_center: Vec2,
147        start_radius: f32,
148        end_center: Vec2,
149        end_radius: f32,
150        stops: Vec<GradientStop>,
151        /// What fills the parameter outside the two circles. See
152        /// [`Shader::LinearGradient`].
153        tile: TileMode,
154    },
155}
156
157impl Shader {
158    /// How many color stops this paint carries, or zero where it carries none.
159    ///
160    /// Public because the threshold is: at or below [`MAX_STOPS`] a gradient
161    /// costs nothing beyond its material, and above it the recorder bakes a
162    /// small texture. Neither is visible in the result, so this exists for a
163    /// caller who cares about the cost rather than the picture.
164    ///
165    /// [`MAX_STOPS`]: impeller_hal::MAX_STOPS
166    pub fn stop_count(&self) -> usize {
167        match self {
168            Self::Solid(_) | Self::Image { .. } | Self::RuntimeEffect { .. } => 0,
169            Self::LinearGradient { stops, .. }
170            | Self::RadialGradient { stops, .. }
171            | Self::SweepGradient { stops, .. }
172            | Self::ConicalGradient { stops, .. } => stops.len(),
173        }
174    }
175
176    /// Whether this would draw anything at all.
177    pub fn is_visible(&self) -> bool {
178        match self {
179            Self::Solid(color) => !color.is_invisible(),
180            // What a caller's program draws is not knowable from here.
181            Self::RuntimeEffect { .. } => true,
182            Self::LinearGradient { stops, .. }
183            | Self::RadialGradient { stops, .. }
184            | Self::SweepGradient { stops, .. }
185            | Self::ConicalGradient { stops, .. } => stops.iter().any(|s| !s.color.is_invisible()),
186            // What the texture holds is unknown here, so only a zero alpha or
187            // an empty destination makes an image provably invisible.
188            Self::Image { alpha, rect, .. } => *alpha > 0.0 && !rect.is_empty(),
189        }
190    }
191}
192
193/// Which part of a blurred shape survives.
194///
195/// A mask blur produces two things a picture can be made from: the shape's own
196/// coverage, and that coverage blurred. Every style here is one combination of
197/// the two, and naming them is what `dart:ui` and Skia both do -- a shadow
198/// wants the blur outside and nothing inside, a glow wants it inside, and a
199/// soft-edged solid wants the shape intact with the blur around it.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
201pub enum MaskBlurStyle {
202    /// The blurred coverage, and nothing else. Soft on both sides of the edge.
203    #[default]
204    Normal,
205    /// The shape at full strength, with the blur outside it.
206    Solid,
207    /// The blur outside the shape only. What a drop shadow behind an opaque
208    /// object is: the part that would be hidden is not drawn at all.
209    Outer,
210    /// The blur inside the shape only, which is an inner shadow or a glow
211    /// confined to what it lights.
212    Inner,
213}
214
215/// A function applied to what a paint drew, rather than to the color it
216/// computed.
217///
218/// The distinction from a color filter is what the input is. A color filter
219/// sees one color at a time and cannot know its neighbors; this sees the
220/// picture, which is what a blur needs.
221///
222/// The distinction from [`Paint::mask_blur`] is what is blurred. A mask blur
223/// blurs coverage and then fills, which is only the same picture as blurring
224/// the result when the fill does not vary -- so it takes a solid color and
225/// refuses anything else. This blurs the result, which is defined for every
226/// paint and is what `dart:ui` means by an image filter. For a solid color the
227/// two agree, and there is a test that says so.
228#[derive(Debug, Clone, PartialEq, Default)]
229pub enum ImageFilter {
230    #[default]
231    None,
232    /// Blur what was drawn, by a standard deviation per axis in device pixels.
233    ///
234    /// Two, because `dart:ui`'s `ImageFilter.blur` takes `sigmaX` and
235    /// `sigmaY` separately and upstream's `MakeBlur` passes both through. A
236    /// blur is two separable passes here already, one along each axis, so the
237    /// second deviation costs nothing but the field: each pass uses its own,
238    /// and a pass whose deviation is zero is skipped rather than run as an
239    /// identity that would resample for no reason.
240    ///
241    /// [`Self::blur`] is the isotropic case, which is nearly every use.
242    Blur { sigma_x: f32, sigma_y: f32 },
243    /// Move what was drawn, in device pixels, by resampling it.
244    ///
245    /// Not the same as drawing under the transform, which is what the canvas's
246    /// own stack does. This draws at the size it was written and then moves
247    /// the finished image, so magnifying gives enlarged pixels where the
248    /// transform stack would give the shape redrawn larger. That difference is
249    /// the reason `dart:ui` has both, and a caller wanting the sharp one
250    /// already has `concat`.
251    Matrix { transform: Transform2D },
252    /// Spread what was drawn, taking the largest sample within these radii in
253    /// device pixels.
254    ///
255    /// `ImageFilter.dilate`. On premultiplied color and channel by channel, so
256    /// a translucent shape spreads its coverage along with its color.
257    Dilate { radius_x: f32, radius_y: f32 },
258    /// Shrink what was drawn, taking the smallest sample within these radii in
259    /// device pixels.
260    ///
261    /// `ImageFilter.erode`, and the dual of [`Self::Dilate`]: what one does to
262    /// a shape the other does to the space around it.
263    Erode { radius_x: f32, radius_y: f32 },
264    /// Recolor what was drawn, rather than what the paint computed.
265    ///
266    /// `dart:ui`'s `ColorFilter` is itself an `ImageFilter`, and upstream's
267    /// `DlImageFilter::MakeColorFilter` is the same thing. A paint carries a
268    /// color filter of its own, so the obvious question is what this adds, and
269    /// the answer is narrower than it first looks: **it can be half of a
270    /// [`Self::Compose`], and `Paint::color_filter` cannot.** Recoloring a
271    /// blurred result, or blurring a recolored one, has no other spelling here.
272    ///
273    /// On a single draw with a solid fill it is the same picture as the paint's
274    /// own filter, and a test pins that rather than leaving it to be discovered.
275    /// A paint's image filter applies per draw -- each draw gets a layer of its
276    /// own -- so there is no arrangement of two draws under one paint for the
277    /// two to disagree about. Where they would disagree is inside a
278    /// composition, which is the case this exists for.
279    Color(ColorFilter),
280    /// Run a caller's fragment program over what was drawn.
281    ///
282    /// `dart:ui`'s `ImageFilter.shader`, and upstream's
283    /// `DlImageFilter::MakeRuntimeEffect`. The program reads its input as a
284    /// texture with an identity transform -- the same guarantee upstream
285    /// re-rasterizes to provide, and one this renderer has by construction: a
286    /// filter is a pass whose target is the size of its source, covered by a
287    /// quad, so a fragment's clip position *is* its texture coordinate. A
288    /// program written for this samples exactly as the fixture programs do.
289    ///
290    /// The uniforms are the whole material block, because a program replaces
291    /// the shader that would have read it. `RUNTIME_FLOATS` is the bound and
292    /// anything past it is dropped, as it is for a runtime effect used as a
293    /// paint.
294    Runtime { program: u32, uniforms: Vec<f32> },
295    /// Filter with `inner`, then filter that result with `outer`.
296    ///
297    /// `ImageFilter.compose`, and the reason this type is not `Copy`: a filter
298    /// that holds filters cannot be a fixed size without a bound on the depth,
299    /// and `dart:ui` puts no bound on it.
300    ///
301    /// A caller can already nest [`Canvas::save_layer`] calls to the same
302    /// effect, and did before this existed. What this adds is that the bounds
303    /// come out right without being worked out by hand: the outer layer has to
304    /// be opened wide enough to hold whatever the inner one grew to, which
305    /// means knowing how far each filter in the chain reaches.
306    ///
307    /// [`Canvas::save_layer`]: crate::Canvas::save_layer
308    Compose {
309        outer: Box<ImageFilter>,
310        inner: Box<ImageFilter>,
311    },
312}
313
314/// How far a blur of the given deviations reaches, as an outset per target axis.
315///
316/// A function rather than a pair of numbers because the answer depends on which
317/// directions the blur will run along, and those come from the transform in
318/// force where the layer opened -- which `paint.rs` has no view of. See
319/// `BlurBasis` in `canvas.rs`.
320pub(crate) type BlurReach<'a> = &'a dyn Fn(Vec2) -> Vec2;
321
322impl ImageFilter {
323    /// A blur of the same deviation along both axes, which is nearly every use.
324    pub fn blur(sigma: f32) -> Self {
325        Self::Blur {
326            sigma_x: sigma,
327            sigma_y: sigma,
328        }
329    }
330
331    /// A blur with a deviation per axis, as `dart:ui`'s `ImageFilter.blur`
332    /// states one.
333    pub fn blur_xy(sigma_x: f32, sigma_y: f32) -> Self {
334        Self::Blur { sigma_x, sigma_y }
335    }
336
337    /// Whether a deviation describes a blur at all.
338    ///
339    /// Stated positively rather than as a negated comparison, which reads
340    /// badly on a type where two values can be incomparable: a sigma that is
341    /// not finite is not a blur, and neither is one that is zero or less.
342    pub(crate) fn blurs(sigma: f32) -> bool {
343        sigma.is_finite() && sigma > 0.0
344    }
345
346    /// Whether this would change anything.
347    pub fn is_identity(&self) -> bool {
348        match self {
349            Self::None => true,
350            // Stated positively rather than as a negated comparison, which
351            // reads badly on a type where two values can be incomparable: a
352            // sigma that is not finite is not a blur, and neither is one that
353            // is zero or less.
354            Self::Blur { sigma_x, sigma_y } => !Self::blurs(*sigma_x) && !Self::blurs(*sigma_y),
355            // A matrix that changes nothing is one that costs a layer for
356            // nothing, so it is worth recognizing.
357            Self::Matrix { transform } => {
358                !transform.is_finite() || *transform == Transform2D::IDENTITY
359            }
360            // Rounded before the comparison, on the same reasoning that rounds
361            // it before it is applied: a radius of a third of a pixel names no
362            // sample the filter could take, so it is not a filter.
363            Self::Dilate { radius_x, radius_y } | Self::Erode { radius_x, radius_y } => {
364                Morphology::dilate(*radius_x, *radius_y).is_identity()
365            }
366            // Composing two filters that each change nothing changes nothing,
367            // and costs two layers to say so.
368            // A color filter that recolors nothing is one, which keeps a
369            // caller who builds one from a default out of a layer they did not
370            // ask to pay for. Asked of the filter rather than compared against
371            // `ColorFilter::None`, which is what this used to do: a blend in
372            // `Dst` mode returns the destination untouched and an identity
373            // matrix recolors nothing, and both paid for a pass and a resample
374            // to arrive at what they were handed. `ColorFilter::is_identity`
375            // knows all three and is the same question.
376            Self::Color(filter) => filter.is_identity(),
377            // Never. What a caller's program does is the caller's business and
378            // nothing here can read it, so the only safe answer is that it does
379            // something -- and answering otherwise would drop the pass that
380            // runs it.
381            Self::Runtime { .. } => false,
382            Self::Compose { outer, inner } => outer.is_identity() && inner.is_identity(),
383        }
384    }
385
386    /// Filter with `inner` first and then with `outer`.
387    ///
388    /// The order `dart:ui` states, which is the order the names suggest once
389    /// you picture them nested: the inner filter is the one closer to what was
390    /// drawn. A composition where either half does nothing is the other half,
391    /// since the layer it would have taken is a copy of an image.
392    pub fn compose(outer: ImageFilter, inner: ImageFilter) -> Self {
393        if inner.is_identity() {
394            return outer;
395        }
396        if outer.is_identity() {
397            return inner;
398        }
399        Self::Compose {
400            outer: Box::new(outer),
401            inner: Box::new(inner),
402        }
403    }
404
405    /// The outermost filter that is not itself a composition, and everything
406    /// that has to run before it.
407    ///
408    /// A chain is applied innermost first, so drawing one means peeling from
409    /// the other end: open a layer for the outermost, and hand the remainder
410    /// back to be drawn inside it. The remainder is a filter in its own right,
411    /// which is what lets the caller recurse without knowing how deep it is.
412    ///
413    /// Recursive on the *outer* half, and that is the whole point. A first
414    /// version took `outer` to be a leaf and returned it directly, which is
415    /// true for anything `compose` builds left to right and false the moment a
416    /// caller composes two compositions -- `compose(compose(a, b), c)` was
417    /// refused as an unimplemented filter, having been assembled entirely out
418    /// of implemented ones.
419    pub(crate) fn peel(&self) -> (ImageFilter, ImageFilter) {
420        match self {
421            Self::Compose { outer, inner } => {
422                let (outermost, rest) = outer.peel();
423                (outermost, Self::compose(rest, (**inner).clone()))
424            }
425            leaf => (leaf.clone(), Self::None),
426        }
427    }
428
429    /// The region of device pixels a target must cover for this filter to run
430    /// over content occupying `min`..`max`.
431    ///
432    /// Both what goes in and what comes out, which is not the same question for
433    /// every filter and is the one that matters. A blur or a dilation only
434    /// grows, so its output contains its input and the distinction is invisible
435    /// -- the case that makes it visible is a matrix, which *moves* the image.
436    /// A composition puts the inner filter's layer inside the outer one's
437    /// target, and a layer's target is clipped to its parent's, so a target
438    /// sized only for where a matrix filter put things would crop the content
439    /// before the matrix ever ran: the inner layer draws the shape where it was
440    /// written and the composite is what moves it.
441    ///
442    /// Erosion is left alone rather than shrunk. Covering more than the result
443    /// needs costs a little memory; covering less loses drawing.
444    pub(crate) fn covering(&self, min: Vec2, max: Vec2, blur: BlurReach) -> (Vec2, Vec2) {
445        match self {
446            // A color filter changes what a pixel is and never which pixels
447            // there are, so it needs exactly the region it was given. A runtime
448            // effect is the same on the side that matters here: it may read
449            // anywhere in its input, and it writes only where the pass covers.
450            Self::None | Self::Erode { .. } | Self::Color(_) | Self::Runtime { .. } => (min, max),
451            Self::Blur { sigma_x, sigma_y } => {
452                // Per axis, which is the whole reason a filter states two: a
453                // blur along one axis alone must not widen the other, or the
454                // target holding it is bigger than the picture in it.
455                //
456                // Which axes, though, is the caller's to say. A blur runs along
457                // the directions the caller's own axes point once the transform
458                // has been applied, so the outset is the bounding box of the two
459                // reaches laid along those -- and asking here with the target's
460                // axes when the blur will run along turned ones sizes a target
461                // for one blur and fills it with another. That is not a
462                // conservative error: it is short exactly where the blur went.
463                let reach = blur(Vec2::new(*sigma_x, *sigma_y));
464                (min - reach, max + reach)
465            }
466            Self::Dilate { radius_x, radius_y } => {
467                let radius = Morphology::dilate(*radius_x, *radius_y).radius;
468                let reach = Vec2::new(radius[0], radius[1]);
469                (min - reach, max + reach)
470            }
471            Self::Matrix { transform } => {
472                // A matrix that carries the content across the vanishing line
473                // moves it further than a box can state, and this is a
474                // statement about how far a filter reaches -- so it widens to
475                // everything rather than reporting a reach that is too short.
476                let (moved_min, moved_max) =
477                    transformed_bounds(*transform, min, max).unwrap_or_else(unbounded);
478                (min.min(moved_min), max.max(moved_max))
479            }
480            Self::Compose { outer, inner } => {
481                let (min, max) = inner.covering(min, max, blur);
482                outer.covering(min, max, blur)
483            }
484        }
485    }
486}
487
488/// Fill the shape, or trace its outline.
489#[derive(Debug, Clone, Copy, PartialEq, Default)]
490pub enum Style {
491    #[default]
492    Fill,
493    /// Stroke with the given width and joinery.
494    ///
495    /// The width is in the coordinate space the shape is drawn in, so it scales
496    /// with the canvas transform. A caller wanting a hairline that stays one
497    /// pixel wide under zoom divides by the current scale.
498    Stroke(StrokeStyle),
499}
500
501/// Everything about how a shape is painted.
502#[derive(Debug, Clone, PartialEq)]
503pub struct Paint {
504    pub shader: Shader,
505    pub style: Style,
506    /// Which part of the blurred shape survives. Ignored without a mask blur.
507    pub mask_blur_style: MaskBlurStyle,
508    /// A function applied to what this paint drew, after the shader and any
509    /// color filter.
510    ///
511    /// Costs a layer: what is drawn goes into a target of its own, is
512    /// filtered, and is composited back. See [`ImageFilter`] for how this
513    /// differs from a color filter and from a mask blur.
514    pub image_filter: ImageFilter,
515    /// A function applied to the color the shader produces, before blending.
516    ///
517    /// Beside the shader rather than inside it because it applies to all of
518    /// them, and after it rather than before because that is where `dart:ui`
519    /// puts a color filter and where it is useful: what is being recolored is
520    /// the result of the fill, not the inputs it was built from.
521    pub color_filter: ColorFilter,
522    /// Cut a stroke into a dash pattern before drawing it.
523    ///
524    /// On the paint rather than inside [`Style::Stroke`] for two reasons. It
525    /// keeps the geometry crate's stroke description a small copyable value,
526    /// which the tessellator wants and a pattern of arbitrary length would
527    /// end. And it says what it is: a dashed stroke is a stroke of a different
528    /// path, so this describes what happens to the path on the way in rather
529    /// than how the outline is built.
530    ///
531    /// Ignored by a fill, which has no length to measure along.
532    pub dash: Option<Dash>,
533    /// Blur the shape's coverage before filling it, in device pixels.
534    ///
535    /// What a soft shadow is made of: the same shape, softened, drawn behind
536    /// the thing casting it. Zero for none.
537    ///
538    /// `dart:ui` states this as blurring the mask and then applying the paint
539    /// through it, and that is what a shape gets. Two orders exist and they
540    /// agree only where the fill does not vary, because a blur is linear:
541    /// `blur(C·α)` is `C·blur(α)` for a constant `C` and for nothing else. A
542    /// solid color therefore takes the cheaper route and is drawn through a
543    /// blurred layer, which is that identity used; anything that varies is
544    /// drawn the way the specification says, with the fill laid across
545    /// everything the blur reaches and blurred coverage composited onto it.
546    ///
547    /// Two things still take a solid color only, and neither is about blurring.
548    /// A glyph run tints one color by its own nature, so a gradient over one is
549    /// refused here exactly as it is when nothing is blurred. And a mesh
550    /// carries a color per vertex, which is a second thing multiplying the
551    /// coverage rather than a fill to lay down.
552    ///
553    /// The three styles that combine a blurred mask with a sharp one need the
554    /// coverage twice and are not built for a varying fill; they take a solid
555    /// color, which is every use a shadow has.
556    pub mask_blur: f32,
557    pub blend: BlendMode,
558    /// How a color carried per vertex or per sprite combines with what this
559    /// paint produced.
560    ///
561    /// `dart:ui` passes this as an argument to `drawVertices` and `drawAtlas`
562    /// rather than on the paint. It sits here instead because those are the
563    /// only two calls that can carry such a color, and an argument every other
564    /// call would have to pass and ignore is worse than a field they leave
565    /// alone. [`BlendMode::Modulate`] multiplies, which is the identity for the
566    /// white a caller who supplied no color gets.
567    ///
568    /// Distinct from [`Self::blend`], which decides how the result reaches the
569    /// target. Both colors here are in the shader already, so every mode is
570    /// available to this one without an extension.
571    pub tint_blend: BlendMode,
572    /// Whether to antialias this shape's edges.
573    ///
574    /// Recorded per paint but applied per pass, because multisampling is a
575    /// property of the target rather than of a draw: a canvas that mixes the
576    /// two antialiases everything.
577    pub anti_alias: bool,
578}
579
580impl Default for Paint {
581    fn default() -> Self {
582        Self {
583            shader: Shader::Solid(Color::BLACK),
584            style: Style::Fill,
585            image_filter: ImageFilter::None,
586            mask_blur_style: MaskBlurStyle::default(),
587            color_filter: ColorFilter::None,
588            dash: None,
589            mask_blur: 0.0,
590            blend: BlendMode::SrcOver,
591            tint_blend: BlendMode::Modulate,
592            anti_alias: true,
593        }
594    }
595}
596
597impl Paint {
598    /// A solid fill.
599    pub fn fill(color: Color) -> Self {
600        Self {
601            shader: Shader::Solid(color),
602            ..Default::default()
603        }
604    }
605
606    /// A fill that runs between colors along a line in user space.
607    ///
608    /// Any number of them. Up to [`MAX_STOPS`] the colors travel with the
609    /// material; beyond that the recorder tabulates them into a texture the
610    /// shader samples, which costs one small upload per distinct gradient and
611    /// is otherwise invisible. Ask [`Shader::stop_count`] if you want to know
612    /// which will happen.
613    ///
614    /// [`MAX_STOPS`]: impeller_hal::MAX_STOPS
615    pub fn linear_gradient(start: Vec2, end: Vec2, stops: Vec<GradientStop>) -> Self {
616        Self {
617            shader: Shader::LinearGradient {
618                start,
619                end,
620                stops,
621                tile: TileMode::default(),
622            },
623            ..Default::default()
624        }
625    }
626
627    /// A fill that runs outward from a center in user space.
628    ///
629    /// Any number of stops; see [`Paint::linear_gradient`].
630    pub fn radial_gradient(center: Vec2, radius: f32, stops: Vec<GradientStop>) -> Self {
631        Self {
632            shader: Shader::RadialGradient {
633                center,
634                radius,
635                stops,
636                tile: TileMode::default(),
637            },
638            ..Default::default()
639        }
640    }
641
642    /// A fill that runs around a center in user space.
643    ///
644    /// Angles are in radians, counter-clockwise from the positive X axis. At
645    /// any number of stops; see [`Paint::linear_gradient`].
646    pub fn sweep_gradient(
647        center: Vec2,
648        start_angle: f32,
649        end_angle: f32,
650        stops: Vec<GradientStop>,
651    ) -> Self {
652        Self {
653            shader: Shader::SweepGradient {
654                center,
655                start_angle,
656                end_angle,
657                stops,
658                tile: TileMode::default(),
659            },
660            ..Default::default()
661        }
662    }
663
664    /// A fill computed by a program the caller supplied.
665    ///
666    /// See [`Shader::RuntimeEffect`]. The program has to be registered with
667    /// the context that will draw the recording, and the index it was given is
668    /// what goes here.
669    pub fn runtime_effect(program: u32, uniforms: Vec<f32>) -> Self {
670        Self {
671            shader: Shader::RuntimeEffect {
672                program,
673                uniforms,
674                images: Vec::new(),
675            },
676            ..Default::default()
677        }
678    }
679
680    /// Give a runtime effect a texture to sample.
681    ///
682    /// Ignored by every other kind of paint, which either samples nothing or
683    /// already names what it samples. Called more than once, the slots stack up
684    /// in the order the program declares its bindings.
685    pub fn with_effect_image(mut self, slot: u32) -> Self {
686        if let Shader::RuntimeEffect { images, .. } = &mut self.shader {
687            images.push(slot);
688        }
689        self
690    }
691
692    /// Give a runtime effect every texture it samples at once.
693    ///
694    /// The order is the order the program declares its bindings, which for a
695    /// program written in WGSL is the order of the binding numbers rather than
696    /// the order of the declarations.
697    pub fn with_effect_images(mut self, slots: &[u32]) -> Self {
698        if let Shader::RuntimeEffect { images, .. } = &mut self.shader {
699            images.clear();
700            images.extend_from_slice(slots);
701        }
702        self
703    }
704
705    /// A fill that runs between two circles in user space.
706    ///
707    /// The general gradient: the first stop lands on the first circle and the
708    /// last on the second, and every stop between them on a circle
709    /// interpolated between the two. `radial_gradient` is the case where the
710    /// first circle is a point at the second's center, and is worth reaching
711    /// for when that is what you mean -- it does less work per fragment.
712    ///
713    /// Any number of stops; see [`Paint::linear_gradient`].
714    pub fn conical_gradient(
715        start_center: Vec2,
716        start_radius: f32,
717        end_center: Vec2,
718        end_radius: f32,
719        stops: Vec<GradientStop>,
720    ) -> Self {
721        Self {
722            shader: Shader::ConicalGradient {
723                start_center,
724                start_radius,
725                end_center,
726                end_radius,
727                stops,
728                tile: TileMode::default(),
729            },
730            ..Default::default()
731        }
732    }
733
734    /// How to read a texture between its texels.
735    ///
736    /// Linear by default, which is what an image drawn at any size but its own
737    /// wants. [`Sampling::Nearest`] is for the cases where blending is the
738    /// wrong answer: pixel art, and a sprite drawn at exactly its own size
739    /// where certainty that no neighbor bled in matters more than smoothness.
740    ///
741    /// Ignored by a paint with no texture.
742    pub fn with_sampling(mut self, sampling: Sampling) -> Self {
743        if let Shader::Image { sampling: at, .. } = &mut self.shader {
744            *at = sampling;
745        }
746        self
747    }
748
749    /// Keep only part of the blurred shape. See [`MaskBlurStyle`].
750    ///
751    /// Ignored unless a mask blur is set, since there is nothing to take a
752    /// part of otherwise.
753    pub fn with_mask_blur_style(mut self, style: MaskBlurStyle) -> Self {
754        self.mask_blur_style = style;
755        self
756    }
757
758    /// Filter what this paint draws, rather than the color it computes.
759    ///
760    /// Unlike a mask blur this takes any shader, because blurring a result is
761    /// defined whatever produced it. It is the more expensive of the two: a
762    /// layer is allocated, drawn into and composited back, where a mask blur
763    /// of a solid color reaches the same picture the same way but is only
764    /// offered where that equivalence holds.
765    pub fn with_image_filter(mut self, filter: ImageFilter) -> Self {
766        self.image_filter = filter;
767        self
768    }
769
770    /// Apply a function to the color this paint produces, before it blends.
771    ///
772    /// Works on any shader, which is the point: the image tint that came
773    /// before it could only recolor an image. A gradient, a shape, a run of
774    /// glyphs all take one now.
775    pub fn with_color_filter(mut self, filter: ColorFilter) -> Self {
776        self.color_filter = filter;
777        self
778    }
779
780    /// Blur the shape's coverage, for a shadow or a glow.
781    ///
782    /// Ignored unless finite and positive. Applies to a fill or a stroke alike
783    /// -- what is blurred is whatever coverage the shape produces.
784    pub fn with_mask_blur(mut self, sigma: f32) -> Self {
785        self.mask_blur = if sigma.is_finite() && sigma > 0.0 {
786            sigma
787        } else {
788            0.0
789        };
790        self
791    }
792
793    /// Dash this paint's stroke, or clear an existing pattern with `None`.
794    ///
795    /// The lengths are in the space the shape is drawn in, so a pattern scales
796    /// with the canvas transform along with the line it cuts. A pattern that
797    /// cannot be walked -- empty, negative, or summing to zero -- draws the
798    /// line whole rather than drawing nothing, since an undashed line leads
799    /// back to the pattern and an absent one leads nowhere.
800    pub fn with_dash(mut self, dash: impl Into<Option<Dash>>) -> Self {
801        self.dash = dash.into();
802        self
803    }
804
805    /// A stroke of the given width, with default caps and joins.
806    /// A fill that samples an image across a rectangle in user space.
807    ///
808    /// `slot` indexes the table supplied when the recording is drawn, and
809    /// `rect` is the region the image covers rather than the region drawn: the
810    /// shape being filled decides what is painted, this decides where the image
811    /// sits under it.
812    pub fn image(slot: u32, rect: Rect) -> Self {
813        Self {
814            shader: Shader::Image {
815                slot,
816                rect,
817                alpha: 1.0,
818                tile: TileMode::default(),
819                source: Rect::new(0.0, 0.0, 1.0, 1.0),
820                tint: Color::WHITE,
821                sampling: Sampling::default(),
822            },
823            ..Default::default()
824        }
825    }
826
827    /// What happens outside the paint's own extent.
828    ///
829    /// Means the same thing for a gradient as for an image, which is why it is
830    /// one call rather than two: past the end of the ramp, past the radius, or
831    /// outside the swept arc, the color either holds, or repeats, or stops.
832    /// Ignored by a solid paint, which has no outside.
833    pub fn with_tile_mode(mut self, tile: TileMode) -> Self {
834        match &mut self.shader {
835            Shader::Image { tile: current, .. }
836            | Shader::LinearGradient { tile: current, .. }
837            | Shader::RadialGradient { tile: current, .. }
838            | Shader::SweepGradient { tile: current, .. }
839            | Shader::ConicalGradient { tile: current, .. } => *current = tile,
840            // Neither has an outside a tile mode could describe: a solid has
841            // no edge, and where a caller's program stops is the caller's
842            // arithmetic rather than this renderer's.
843            Shader::Solid(_) | Shader::RuntimeEffect { .. } => {}
844        }
845        self
846    }
847
848    /// Draw only part of the image, in coordinates from zero to one.
849    ///
850    /// What a sprite sheet needs, and a nine-patch border. Normalized rather
851    /// than in texels because a canvas records without touching a device and
852    /// has never seen the image: it holds a slot, not a texture, and cannot ask
853    /// how large it is. The caller who uploaded it can, which is what
854    /// [`Paint::with_source_pixels`] is for.
855    ///
856    /// Ignored by paints that are not images.
857    pub fn with_source(mut self, source: Rect) -> Self {
858        if let Shader::Image { source: at, .. } = &mut self.shader {
859            *at = source;
860        }
861        self
862    }
863
864    /// The same, stated in texels of an image of the given size.
865    ///
866    /// A degenerate size would divide by zero, so it leaves the paint alone --
867    /// drawing the whole image, which is what the paint already said.
868    pub fn with_source_pixels(self, source: Rect, size: Extent2D) -> Self {
869        if size.width == 0 || size.height == 0 {
870            return self;
871        }
872        let (w, h) = (size.width as f32, size.height as f32);
873        self.with_source(Rect::new(
874            source.left / w,
875            source.top / h,
876            source.right / w,
877            source.bottom / h,
878        ))
879    }
880
881    /// Multiply an image paint's sampled color. Ignored by other paints.
882    ///
883    /// What turns one monochrome icon sheet into every state a control has:
884    /// upload the shapes once as white on transparent, and tint per draw. A
885    /// white tint is the identity, which is what an image that never mentions
886    /// one already carries.
887    ///
888    /// Composes with everything else on the paint, so a tinted sprite from a
889    /// sheet is one call for the piece and one for the color.
890    pub fn with_tint(mut self, tint: Color) -> Self {
891        if let Shader::Image { tint: at, .. } = &mut self.shader {
892            *at = tint;
893        }
894        self
895    }
896
897    /// Scale an image paint's sampled color. Ignored by other paints.
898    pub fn with_image_alpha(mut self, alpha: f32) -> Self {
899        if let Shader::Image { alpha: current, .. } = &mut self.shader {
900            *current = alpha;
901        }
902        self
903    }
904
905    pub fn stroke(color: Color, width: f32) -> Self {
906        Self {
907            shader: Shader::Solid(color),
908            style: Style::Stroke(StrokeStyle::new(width)),
909            ..Default::default()
910        }
911    }
912
913    pub fn with_shader(mut self, shader: Shader) -> Self {
914        self.shader = shader;
915        self
916    }
917
918    pub fn with_style(mut self, style: Style) -> Self {
919        self.style = style;
920        self
921    }
922
923    pub fn with_blend(mut self, blend: BlendMode) -> Self {
924        self.blend = blend;
925        self
926    }
927
928    /// How a per-vertex or per-sprite color combines with this paint's result.
929    ///
930    /// Ignored by every call that carries no such color, there being nothing to
931    /// combine.
932    pub fn with_tint_blend(mut self, tint_blend: BlendMode) -> Self {
933        self.tint_blend = tint_blend;
934        self
935    }
936
937    /// Whether this paint's edges are smoothed. On by default.
938    ///
939    /// How that is paid for depends on the shape rather than on this flag. A
940    /// rectangle, rounded rectangle, oval or circle is drawn from a distance
941    /// field and antialiases itself inside its own fragment shader, costing a
942    /// pass nothing. Anything that reaches the tessellator instead -- an
943    /// arbitrary path, a stroke, a line, a double rounded rectangle -- has only
944    /// triangles to work with, so its pass becomes multisampled.
945    ///
946    /// That second case carries a requirement worth knowing before meeting it:
947    /// a multisampled pass must clear, so the pass it lands in needs something
948    /// to clear to. Drawn straight onto the frame, that means the canvas needs
949    /// a background -- see [`crate::Canvas::clear`], which is where a caller
950    /// refused for this reason should look. Drawn inside a layer it means
951    /// nothing to the caller, the layer's target clearing to transparent on
952    /// its own.
953    pub fn with_anti_alias(mut self, anti_alias: bool) -> Self {
954        self.anti_alias = anti_alias;
955        self
956    }
957
958    /// Whether drawing with this paint would change anything.
959    ///
960    /// A transparent fill and a zero-width stroke both draw nothing, and
961    /// skipping them early keeps empty geometry out of the batch rather than
962    /// tessellating it and discovering it was empty.
963    pub fn is_visible(&self) -> bool {
964        if !self.shader.is_visible() {
965            return false;
966        }
967        match &self.style {
968            Style::Fill => true,
969            // `can_draw` rather than `is_visible`, and the difference is a
970            // width of zero: a hairline, which the canvas widens to a device
971            // pixel before any geometry is built for it.
972            Style::Stroke(stroke) => stroke.can_draw(),
973        }
974    }
975}
976
977#[cfg(test)]
978mod tests {
979    use super::*;
980
981    #[test]
982    fn a_default_paint_is_an_opaque_antialiased_fill() {
983        let paint = Paint::default();
984        assert_eq!(paint.style, Style::Fill);
985        assert_eq!(paint.blend, BlendMode::SrcOver);
986        assert!(paint.anti_alias);
987        assert!(paint.is_visible());
988    }
989
990    #[test]
991    fn a_transparent_paint_draws_nothing_whatever_its_style() {
992        let clear = Color::WHITE.with_alpha(0.0);
993        assert!(!Paint::fill(clear).is_visible());
994        assert!(!Paint::stroke(clear, 4.0).is_visible());
995
996        // A gradient every stop of which is transparent draws nothing either.
997        let invisible = Paint::linear_gradient(
998            Vec2::ZERO,
999            Vec2::new(1.0, 0.0),
1000            vec![GradientStop::new(clear, 0.0), GradientStop::new(clear, 1.0)],
1001        );
1002        assert!(!invisible.is_visible());
1003    }
1004
1005    #[test]
1006    fn a_zero_width_stroke_draws_nothing() {
1007        // Animating a width to zero should stop drawing rather than emit
1008        // degenerate geometry for the tessellator to discard.
1009        // A width of zero is a hairline rather than nothing, so the paint is
1010        // visible; a negative one still is not a stroke.
1011        assert!(Paint::stroke(Color::BLACK, 0.0).is_visible());
1012        assert!(!Paint::stroke(Color::BLACK, -1.0).is_visible());
1013        assert!(Paint::stroke(Color::BLACK, 0.5).is_visible());
1014    }
1015}