vello_common/
encode.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Paints for drawing shapes.
5
6use crate::blurred_rounded_rect::BlurredRoundedRectangle;
7use crate::color::palette::css::BLACK;
8use crate::color::{ColorSpaceTag, HueDirection, Srgb, gradient};
9use crate::kurbo::{Affine, Point, Vec2};
10use crate::math::{FloatExt, compute_erf7};
11use crate::paint::{Image, ImageSource, IndexedPaint, Paint, PremulColor};
12use crate::peniko::{ColorStop, Extend, Gradient, GradientKind, ImageQuality};
13use alloc::borrow::Cow;
14use alloc::fmt::Debug;
15use alloc::vec;
16use alloc::vec::Vec;
17#[cfg(not(feature = "multithreading"))]
18use core::cell::OnceCell;
19use fearless_simd::{Simd, SimdBase, SimdFloat, f32x4, f32x16};
20use smallvec::{SmallVec, ToSmallVec};
21// So we can just use `OnceCell` regardless of which feature is activated.
22#[cfg(feature = "multithreading")]
23use std::sync::OnceLock as OnceCell;
24
25use crate::simd::{Splat4thExt, element_wise_splat};
26#[cfg(not(feature = "std"))]
27use peniko::kurbo::common::FloatFuncs as _;
28
29const DEGENERATE_THRESHOLD: f32 = 1.0e-6;
30const NUDGE_VAL: f32 = 1.0e-7;
31const PIXEL_CENTER_OFFSET: f64 = 0.5;
32
33#[cfg(feature = "std")]
34fn exp(val: f32) -> f32 {
35    val.exp()
36}
37
38#[cfg(not(feature = "std"))]
39fn exp(val: f32) -> f32 {
40    #[cfg(feature = "libm")]
41    return libm::expf(val);
42    #[cfg(not(feature = "libm"))]
43    compile_error!("vello_common requires either the `std` or `libm` feature");
44}
45
46/// A trait for encoding gradients.
47pub trait EncodeExt: private::Sealed {
48    /// Encode the gradient and push it into a vector of encoded paints, returning
49    /// the corresponding paint in the process. This will also validate the gradient.
50    fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint;
51}
52
53impl EncodeExt for Gradient {
54    /// Encode the gradient into a paint.
55    fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
56        // First make sure that the gradient is valid and not degenerate.
57        if let Err(paint) = validate(self) {
58            return paint;
59        }
60
61        let mut has_opacities = self.stops.iter().any(|s| s.color.components[3] != 1.0);
62        let pad = self.extend == Extend::Pad;
63
64        let mut base_transform;
65
66        let mut stops = Cow::Borrowed(&self.stops.0);
67
68        let first_stop = &stops[0];
69        let last_stop = &stops[stops.len() - 1];
70
71        if first_stop.offset != 0.0 || last_stop.offset != 1.0 {
72            let mut vec = stops.to_smallvec();
73
74            if first_stop.offset != 0.0 {
75                let mut first_stop = *first_stop;
76                first_stop.offset = 0.0;
77                vec.insert(0, first_stop);
78            }
79
80            if last_stop.offset != 1.0 {
81                let mut last_stop = *last_stop;
82                last_stop.offset = 1.0;
83                vec.push(last_stop);
84            }
85
86            stops = Cow::Owned(vec);
87        }
88
89        let kind = match self.kind {
90            GradientKind::Linear {
91                start: p0,
92                end: mut p1,
93            } => {
94                // Double the length of the iterator, and append stops in reverse order in case
95                // we have the extend `Reflect`.
96                // Then we can treat it the same as a repeated gradient.
97                if self.extend == Extend::Reflect {
98                    p1.x += p1.x - p0.x;
99                    p1.y += p1.y - p0.y;
100                    stops = Cow::Owned(apply_reflect(&stops));
101                }
102
103                // We update the transform currently in-place, such that the gradient line always
104                // starts at the point (0, 0) and ends at the point (1, 0). This simplifies the
105                // calculation for the current position along the gradient line a lot.
106                base_transform = ts_from_line_to_line(p0, p1, Point::ZERO, Point::new(1.0, 0.0));
107
108                EncodedKind::Linear(LinearKind)
109            }
110            GradientKind::Radial {
111                start_center: c0,
112                start_radius: r0,
113                end_center: mut c1,
114                end_radius: mut r1,
115            } => {
116                // The implementation of radial gradients is translated from Skia.
117                // See:
118                // - <https://skia.org/docs/dev/design/conical/>
119                // - <https://github.com/google/skia/blob/main/src/shaders/gradients/SkConicalGradient.h>
120                // - <https://github.com/google/skia/blob/main/src/shaders/gradients/SkConicalGradient.cpp>
121
122                // Same story as for linear gradients, mutate stops so that reflect and repeat
123                // can be treated the same.
124                if self.extend == Extend::Reflect {
125                    c1 += c1 - c0;
126                    r1 += r1 - r0;
127                    stops = Cow::Owned(apply_reflect(&stops));
128                }
129
130                let d_radius = r1 - r0;
131
132                // <https://github.com/google/skia/blob/1e07a4b16973cf716cb40b72dd969e961f4dd950/src/shaders/gradients/SkConicalGradient.cpp#L83-L112>
133                let radial_kind = if ((c1 - c0).length() as f32).is_nearly_zero() {
134                    base_transform = Affine::translate((-c1.x, -c1.y));
135                    base_transform = base_transform.then_scale(1.0 / r0.max(r1) as f64);
136
137                    let scale = r1.max(r0) / d_radius;
138                    let bias = -r0 / d_radius;
139
140                    RadialKind::Radial { bias, scale }
141                } else {
142                    base_transform =
143                        ts_from_line_to_line(c0, c1, Point::ZERO, Point::new(1.0, 0.0));
144
145                    if (r1 - r0).is_nearly_zero() {
146                        let scaled_r0 = r1 / (c1 - c0).length() as f32;
147                        RadialKind::Strip {
148                            scaled_r0_squared: scaled_r0 * scaled_r0,
149                        }
150                    } else {
151                        let d_center = (c0 - c1).length() as f32;
152
153                        let focal_data =
154                            FocalData::create(r0 / d_center, r1 / d_center, &mut base_transform);
155
156                        let fp0 = 1.0 / focal_data.fr1;
157                        let fp1 = focal_data.f_focal_x;
158
159                        RadialKind::Focal {
160                            focal_data,
161                            fp0,
162                            fp1,
163                        }
164                    }
165                };
166
167                // Even if the gradient has no stops with transparency, we might have to force
168                // alpha-compositing in case the radial gradient is undefined in certain positions,
169                // in which case the resulting color will be transparent and thus the gradient overall
170                // must be treated as non-opaque.
171                has_opacities |= radial_kind.has_undefined();
172
173                EncodedKind::Radial(radial_kind)
174            }
175            GradientKind::Sweep {
176                center,
177                start_angle,
178                end_angle,
179            } => {
180                // For sweep gradients, the position on the "color line" is defined by the
181                // angle towards the gradient center.
182                let start_angle = start_angle.to_radians();
183                let mut end_angle = end_angle.to_radians();
184
185                // Same as before, reduce `Reflect` to `Repeat`.
186                if self.extend == Extend::Reflect {
187                    end_angle += end_angle - start_angle;
188                    stops = Cow::Owned(apply_reflect(&stops));
189                }
190
191                // Make sure the center of the gradient falls on the origin (0, 0), to make
192                // angle calculation easier.
193                let x_offset = -center.x as f32;
194                let y_offset = -center.y as f32;
195                base_transform = Affine::translate((x_offset as f64, y_offset as f64));
196
197                EncodedKind::Sweep(SweepKind {
198                    start_angle,
199                    // Save the inverse so that we can use a multiplication in the shader instead.
200                    inv_angle_delta: 1.0 / (end_angle - start_angle),
201                })
202            }
203        };
204
205        let ranges = encode_stops(&stops, self.interpolation_cs, self.hue_direction);
206
207        // This represents the transform that needs to be applied to the starting point of a
208        // command before starting with the rendering.
209        // First we need to account for the base transform of the shader, then
210        // we account for the fact that we sample in the center of a pixel and not in the corner by
211        // adding 0.5.
212        // Finally, we need to apply the _inverse_ paint transform to the point so that we can account
213        // for the paint transform of the render context.
214        let transform = base_transform
215            * transform.inverse()
216            * Affine::translate((PIXEL_CENTER_OFFSET, PIXEL_CENTER_OFFSET));
217
218        // One possible approach of calculating the positions would be to apply the above
219        // transform to _each_ pixel that we render in the wide tile. However, a much better
220        // approach is to apply the transform once for the first pixel in each wide tile,
221        // and from then on only apply incremental updates to the current x/y position
222        // that we calculate based on the transform.
223        //
224        // Remember that we render wide tiles in column major order (i.e. we first calculate the
225        // values for a specific x for all Tile::HEIGHT y by incrementing y by 1, and then finally
226        // we increment the x position by 1 and start from the beginning). If we want to implement
227        // the above approach of incrementally updating the position, we need to calculate
228        // how the x/y unit vectors are affected by the transform, and then use this as the
229        // step delta for a step in the x/y direction.
230        let (x_advance, y_advance) = x_y_advances(&transform);
231
232        let encoded = EncodedGradient {
233            kind,
234            transform,
235            x_advance,
236            y_advance,
237            ranges,
238            pad,
239            has_opacities,
240            u8_lut: OnceCell::new(),
241            f32_lut: OnceCell::new(),
242        };
243
244        let idx = paints.len();
245        paints.push(encoded.into());
246
247        Paint::Indexed(IndexedPaint::new(idx))
248    }
249}
250
251/// Returns a fallback paint in case the gradient is invalid.
252///
253/// The paint will be either black or contain the color of the first stop of the gradient.
254fn validate(gradient: &Gradient) -> Result<(), Paint> {
255    let black = Err(BLACK.into());
256
257    // Gradients need at least two stops.
258    if gradient.stops.is_empty() {
259        return black;
260    }
261
262    let first = Err(gradient.stops[0].color.to_alpha_color::<Srgb>().into());
263
264    if gradient.stops.len() == 1 {
265        return first;
266    }
267
268    for stops in gradient.stops.windows(2) {
269        let f = stops[0];
270        let n = stops[1];
271
272        // Offsets must be between 0 and 1.
273        if f.offset > 1.0 || f.offset < 0.0 {
274            return first;
275        }
276
277        // Stops must be sorted by ascending offset.
278        if f.offset > n.offset {
279            return first;
280        }
281    }
282
283    let degenerate_point = |p1: &Point, p2: &Point| {
284        (p1.x - p2.x).abs() as f32 <= DEGENERATE_THRESHOLD
285            && (p1.y - p2.y).abs() as f32 <= DEGENERATE_THRESHOLD
286    };
287
288    let degenerate_val = |v1: f32, v2: f32| (v2 - v1).abs() <= DEGENERATE_THRESHOLD;
289
290    match &gradient.kind {
291        GradientKind::Linear { start, end } => {
292            // Start and end points must not be too close together.
293            if degenerate_point(start, end) {
294                return first;
295            }
296        }
297        GradientKind::Radial {
298            start_center,
299            start_radius,
300            end_center,
301            end_radius,
302        } => {
303            // Radii must not be negative.
304            if *start_radius < 0.0 || *end_radius < 0.0 {
305                return first;
306            }
307
308            // Radii and center points must not be close to the same.
309            if degenerate_point(start_center, end_center)
310                && degenerate_val(*start_radius, *end_radius)
311            {
312                return first;
313            }
314        }
315        GradientKind::Sweep {
316            start_angle,
317            end_angle,
318            ..
319        } => {
320            // The end angle must be larger than the start angle.
321            if degenerate_val(*start_angle, *end_angle) {
322                return first;
323            }
324
325            if end_angle <= start_angle {
326                return first;
327            }
328        }
329    }
330
331    Ok(())
332}
333
334/// Extend the stops so that we can treat a repeated gradient like a reflected gradient.
335fn apply_reflect(stops: &[ColorStop]) -> SmallVec<[ColorStop; 4]> {
336    let first_half = stops.iter().map(|s| ColorStop {
337        offset: s.offset / 2.0,
338        color: s.color,
339    });
340
341    let second_half = stops.iter().rev().map(|s| ColorStop {
342        offset: 0.5 + (1.0 - s.offset) / 2.0,
343        color: s.color,
344    });
345
346    first_half.chain(second_half).collect::<SmallVec<_>>()
347}
348
349/// Encode all stops into a sequence of ranges.
350fn encode_stops(
351    stops: &[ColorStop],
352    cs: ColorSpaceTag,
353    hue_dir: HueDirection,
354) -> Vec<GradientRange> {
355    #[derive(Debug)]
356    struct EncodedColorStop {
357        offset: f32,
358        color: crate::color::PremulColor<Srgb>,
359    }
360
361    let create_range = |left_stop: &EncodedColorStop, right_stop: &EncodedColorStop| {
362        let clamp = |mut color: [f32; 4]| {
363            // The linear approximation of the gradient can produce values slightly outside of
364            // [0.0, 1.0], so clamp them.
365            for c in &mut color {
366                *c = c.clamp(0.0, 1.0);
367            }
368
369            color
370        };
371
372        let x0 = left_stop.offset;
373        let x1 = right_stop.offset;
374        let c0 = clamp(left_stop.color.components);
375        let c1 = clamp(right_stop.color.components);
376
377        // We calculate a bias and scale factor, such that we can simply calculate
378        // bias + x * scale to get the interpolated color, where x is between x0 and x1,
379        // to calculate the resulting color.
380        // Apply a nudge value because we sometimes call `create_range` with the same offset
381        // to create the padded stops.
382        let x1_minus_x0 = (x1 - x0).max(NUDGE_VAL);
383        let mut scale = [0.0; 4];
384        let mut bias = c0;
385
386        for i in 0..4 {
387            scale[i] = (c1[i] - c0[i]) / x1_minus_x0;
388            bias[i] = c0[i] - x0 * scale[i];
389        }
390
391        GradientRange { x1, bias, scale }
392    };
393
394    // Create additional (SRGB-encoded) stops in-between to approximate the color space we want to
395    // interpolate in.
396    if cs != ColorSpaceTag::Srgb {
397        let interpolated_stops = stops
398            .windows(2)
399            .flat_map(|s| {
400                let left_stop = &s[0];
401                let right_stop = &s[1];
402
403                let interpolated =
404                    gradient::<Srgb>(left_stop.color, right_stop.color, cs, hue_dir, 0.01);
405
406                interpolated.map(|st| EncodedColorStop {
407                    offset: left_stop.offset + (right_stop.offset - left_stop.offset) * st.0,
408                    color: st.1,
409                })
410            })
411            .collect::<Vec<_>>();
412
413        interpolated_stops
414            .windows(2)
415            .map(|s| {
416                let left_stop = &s[0];
417                let right_stop = &s[1];
418
419                create_range(left_stop, right_stop)
420            })
421            .collect()
422    } else {
423        stops
424            .windows(2)
425            .map(|c| {
426                let c0 = EncodedColorStop {
427                    offset: c[0].offset,
428                    color: c[0].color.to_alpha_color::<Srgb>().premultiply(),
429                };
430
431                let c1 = EncodedColorStop {
432                    offset: c[1].offset,
433                    color: c[1].color.to_alpha_color::<Srgb>().premultiply(),
434                };
435
436                create_range(&c0, &c1)
437            })
438            .collect()
439    }
440}
441
442pub(crate) fn x_y_advances(transform: &Affine) -> (Vec2, Vec2) {
443    let scale_skew_transform = {
444        let c = transform.as_coeffs();
445        Affine::new([c[0], c[1], c[2], c[3], 0.0, 0.0])
446    };
447
448    let x_advance = scale_skew_transform * Point::new(1.0, 0.0);
449    let y_advance = scale_skew_transform * Point::new(0.0, 1.0);
450
451    (
452        Vec2::new(x_advance.x, x_advance.y),
453        Vec2::new(y_advance.x, y_advance.y),
454    )
455}
456
457impl private::Sealed for Image {}
458
459impl EncodeExt for Image {
460    fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
461        let idx = paints.len();
462
463        let mut quality = self.quality;
464
465        let c = transform.as_coeffs();
466
467        // Optimize image quality for integer-only translations.
468        if (c[0] as f32 - 1.0).is_nearly_zero()
469            && (c[1] as f32).is_nearly_zero()
470            && (c[2] as f32).is_nearly_zero()
471            && (c[3] as f32 - 1.0).is_nearly_zero()
472            && ((c[4] - c[4].floor()) as f32).is_nearly_zero()
473            && ((c[5] - c[5].floor()) as f32).is_nearly_zero()
474            && quality == ImageQuality::Medium
475        {
476            quality = ImageQuality::Low;
477        }
478
479        // Similarly to gradients, apply a 0.5 offset so we sample at the center of
480        // a pixel.
481        let transform = transform.inverse() * Affine::translate((0.5, 0.5));
482
483        let (x_advance, y_advance) = x_y_advances(&transform);
484
485        let encoded = match &self.source {
486            ImageSource::Pixmap(pixmap) => {
487                EncodedImage {
488                    source: ImageSource::Pixmap(pixmap.clone()),
489                    extends: (self.x_extend, self.y_extend),
490                    quality,
491                    // While we could optimize RGB8 images, it's probably not worth the trouble.
492                    has_opacities: true,
493                    transform,
494                    x_advance,
495                    y_advance,
496                }
497            }
498            ImageSource::OpaqueId(image) => EncodedImage {
499                source: ImageSource::OpaqueId(*image),
500                extends: (self.x_extend, self.y_extend),
501                quality: self.quality,
502                has_opacities: true,
503                transform,
504                x_advance,
505                y_advance,
506            },
507        };
508
509        paints.push(EncodedPaint::Image(encoded));
510
511        Paint::Indexed(IndexedPaint::new(idx))
512    }
513}
514
515/// An encoded paint.
516#[derive(Debug)]
517pub enum EncodedPaint {
518    /// An encoded gradient.
519    Gradient(EncodedGradient),
520    /// An encoded image.
521    Image(EncodedImage),
522    /// A blurred, rounded rectangle.
523    BlurredRoundedRect(EncodedBlurredRoundedRectangle),
524}
525
526impl From<EncodedGradient> for EncodedPaint {
527    fn from(value: EncodedGradient) -> Self {
528        Self::Gradient(value)
529    }
530}
531
532impl From<EncodedBlurredRoundedRectangle> for EncodedPaint {
533    fn from(value: EncodedBlurredRoundedRectangle) -> Self {
534        Self::BlurredRoundedRect(value)
535    }
536}
537
538/// An encoded image.
539#[derive(Debug)]
540pub struct EncodedImage {
541    /// The underlying pixmap of the image.
542    pub source: ImageSource,
543    /// The extends in the horizontal and vertical direction.
544    pub extends: (Extend, Extend),
545    /// The rendering quality of the image.
546    pub quality: ImageQuality,
547    /// Whether the image has opacities.
548    pub has_opacities: bool,
549    /// A transform to apply to the image.
550    pub transform: Affine,
551    /// The advance in image coordinates for one step in the x direction.
552    pub x_advance: Vec2,
553    /// The advance in image coordinates for one step in the y direction.
554    pub y_advance: Vec2,
555}
556
557/// Computed properties of a linear gradient.
558#[derive(Debug, Copy, Clone)]
559pub struct LinearKind;
560
561/// Focal data for a radial gradient.
562#[derive(Debug, PartialEq, Copy, Clone)]
563pub struct FocalData {
564    /// The normalized radius of the outer circle in focal space.
565    pub fr1: f32,
566    /// The x-coordinate of the focal point in normalized space \[0,1\].
567    pub f_focal_x: f32,
568    /// Whether the focal points have been swapped.
569    pub f_is_swapped: bool,
570}
571
572impl FocalData {
573    /// Create a new `FocalData` with the given radii and update the matrix.
574    pub fn create(mut r0: f32, mut r1: f32, matrix: &mut Affine) -> Self {
575        let mut swapped = false;
576        let mut f_focal_x = r0 / (r0 - r1);
577
578        if (f_focal_x - 1.0).is_nearly_zero() {
579            *matrix = matrix.then_translate(Vec2::new(-1.0, 0.0));
580            *matrix = matrix.then_scale_non_uniform(-1.0, 1.0);
581            core::mem::swap(&mut r0, &mut r1);
582            f_focal_x = 0.0;
583            swapped = true;
584        }
585
586        let focal_matrix = ts_from_line_to_line(
587            Point::new(f_focal_x as f64, 0.0),
588            Point::new(1.0, 0.0),
589            Point::new(0.0, 0.0),
590            Point::new(1.0, 0.0),
591        );
592        *matrix = focal_matrix * *matrix;
593
594        let fr1 = r1 / (1.0 - f_focal_x).abs();
595
596        let data = Self {
597            fr1,
598            f_focal_x,
599            f_is_swapped: swapped,
600        };
601
602        if data.is_focal_on_circle() {
603            *matrix = matrix.then_scale(0.5);
604        } else {
605            *matrix = matrix.then_scale_non_uniform(
606                (fr1 / (fr1 * fr1 - 1.0)) as f64,
607                1.0 / (fr1 * fr1 - 1.0).abs().sqrt() as f64,
608            );
609        }
610
611        *matrix = matrix.then_scale((1.0 - f_focal_x).abs() as f64);
612
613        data
614    }
615
616    /// Whether the focal is on the circle.
617    pub fn is_focal_on_circle(&self) -> bool {
618        (1.0 - self.fr1).is_nearly_zero()
619    }
620
621    /// Whether the focal points have been swapped.
622    pub fn is_swapped(&self) -> bool {
623        self.f_is_swapped
624    }
625
626    /// Whether the gradient is well-behaved.
627    pub fn is_well_behaved(&self) -> bool {
628        !self.is_focal_on_circle() && self.fr1 > 1.0
629    }
630
631    /// Whether the gradient is natively focal.
632    pub fn is_natively_focal(&self) -> bool {
633        self.f_focal_x.is_nearly_zero()
634    }
635}
636
637/// A radial gradient.
638#[derive(Debug, PartialEq, Copy, Clone)]
639pub enum RadialKind {
640    /// A radial gradient, i.e. the start and end center points are the same.
641    Radial {
642        /// The `bias` value (from the Skia implementation).
643        ///
644        /// It is a correction factor that accounts for the fact that the focal center might not
645        /// lie on the inner circle (if r0 > 0).
646        bias: f32,
647        /// The `scale` value (from the Skia implementation).
648        ///
649        /// It is a scaling factor that maps from r0 to r1.
650        scale: f32,
651    },
652    /// A strip gradient, i.e. the start and end radius are the same.
653    Strip {
654        /// The squared value of `scaled_r0` (from the Skia implementation).
655        scaled_r0_squared: f32,
656    },
657    /// A general, two-point conical gradient.
658    Focal {
659        /// The focal data  (from the Skia implementation).
660        focal_data: FocalData,
661        /// The `fp0` value (from the Skia implementation).
662        fp0: f32,
663        /// The `fp1` value (from the Skia implementation).
664        fp1: f32,
665    },
666}
667
668impl RadialKind {
669    /// Whether the gradient is undefined at any location.
670    pub fn has_undefined(&self) -> bool {
671        match self {
672            Self::Radial { .. } => false,
673            Self::Strip { .. } => true,
674            Self::Focal { focal_data, .. } => !focal_data.is_well_behaved(),
675        }
676    }
677}
678
679/// Computed properties of a sweep gradient.
680#[derive(Debug)]
681pub struct SweepKind {
682    /// The start angle of the sweep gradient.
683    pub start_angle: f32,
684    /// The inverse delta between start and end angle.
685    pub inv_angle_delta: f32,
686}
687
688/// A kind of encoded gradient.
689#[derive(Debug)]
690pub enum EncodedKind {
691    /// An encoded linear gradient.
692    Linear(LinearKind),
693    /// An encoded radial gradient.
694    Radial(RadialKind),
695    /// An encoded sweep gradient.
696    Sweep(SweepKind),
697}
698
699/// An encoded gradient.
700#[derive(Debug)]
701pub struct EncodedGradient {
702    /// The underlying kind of gradient.
703    pub kind: EncodedKind,
704    /// A transform that needs to be applied to the position of the first processed pixel.
705    pub transform: Affine,
706    /// How much to advance into the x/y direction for one step in the x direction.
707    pub x_advance: Vec2,
708    /// How much to advance into the x/y direction for one step in the y direction.
709    pub y_advance: Vec2,
710    /// The color ranges of the gradient.
711    pub ranges: Vec<GradientRange>,
712    /// Whether the gradient should be padded.
713    pub pad: bool,
714    /// Whether the gradient requires `source_over` compositing.
715    pub has_opacities: bool,
716    u8_lut: OnceCell<GradientLut<u8>>,
717    f32_lut: OnceCell<GradientLut<f32>>,
718}
719
720impl EncodedGradient {
721    /// Get the lookup table for sampling u8-based gradient values.
722    pub fn u8_lut<S: Simd>(&self, simd: S) -> &GradientLut<u8> {
723        self.u8_lut
724            .get_or_init(|| GradientLut::new(simd, &self.ranges))
725    }
726
727    /// Get the lookup table for sampling f32-based gradient values.
728    pub fn f32_lut<S: Simd>(&self, simd: S) -> &GradientLut<f32> {
729        self.f32_lut
730            .get_or_init(|| GradientLut::new(simd, &self.ranges))
731    }
732}
733
734/// An encoded ange between two color stops.
735#[derive(Debug, Clone)]
736pub struct GradientRange {
737    /// The end value of the range.
738    pub x1: f32,
739    /// A bias to apply when interpolating the color (in this case just the values of the start
740    /// color of the gradient).
741    pub bias: [f32; 4],
742    /// The scale factors of the range. By calculating bias + x * factors (where x is
743    /// between 0.0 and 1.0), we can interpolate between start and end color of the gradient range.
744    pub scale: [f32; 4],
745}
746
747/// An encoded blurred, rounded rectangle.
748#[derive(Debug)]
749pub struct EncodedBlurredRoundedRectangle {
750    /// An component for computing the blur effect.
751    pub exponent: f32,
752    /// An component for computing the blur effect.
753    pub recip_exponent: f32,
754    /// An component for computing the blur effect.
755    pub scale: f32,
756    /// An component for computing the blur effect.
757    pub std_dev_inv: f32,
758    /// An component for computing the blur effect.
759    pub min_edge: f32,
760    /// An component for computing the blur effect.
761    pub w: f32,
762    /// An component for computing the blur effect.
763    pub h: f32,
764    /// An component for computing the blur effect.
765    pub width: f32,
766    /// An component for computing the blur effect.
767    pub height: f32,
768    /// An component for computing the blur effect.
769    pub r1: f32,
770    /// The base color for the blurred rectangle.
771    pub color: PremulColor,
772    /// A transform that needs to be applied to the position of the first processed pixel.
773    pub transform: Affine,
774    /// How much to advance into the x/y direction for one step in the x direction.
775    pub x_advance: Vec2,
776    /// How much to advance into the x/y direction for one step in the y direction.
777    pub y_advance: Vec2,
778}
779
780impl private::Sealed for BlurredRoundedRectangle {}
781
782impl EncodeExt for BlurredRoundedRectangle {
783    fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
784        let rect = {
785            // Ensure rectangle has positive width/height.
786            let mut rect = self.rect;
787
788            if self.rect.x0 > self.rect.x1 {
789                core::mem::swap(&mut rect.x0, &mut rect.x1);
790            }
791
792            if self.rect.y0 > self.rect.y1 {
793                core::mem::swap(&mut rect.y0, &mut rect.y1);
794            }
795
796            rect
797        };
798
799        let transform = Affine::translate((-rect.x0, -rect.y0)) * transform.inverse();
800
801        let (x_advance, y_advance) = x_y_advances(&transform);
802
803        let width = rect.width() as f32;
804        let height = rect.height() as f32;
805        let radius = self.radius.min(0.5 * width.min(height));
806
807        // To avoid divide by 0; potentially should be a bigger number for antialiasing.
808        let std_dev = self.std_dev.max(1e-6);
809
810        let min_edge = width.min(height);
811        let rmax = 0.5 * min_edge;
812        let r0 = radius.hypot(std_dev * 1.15).min(rmax);
813        let r1 = radius.hypot(std_dev * 2.0).min(rmax);
814
815        let exponent = 2.0 * r1 / r0;
816
817        let std_dev_inv = std_dev.recip();
818
819        // Pull in long end (make less eccentric).
820        let delta = 1.25
821            * std_dev
822            * (exp(-(0.5 * std_dev_inv * width).powi(2))
823                - exp(-(0.5 * std_dev_inv * height).powi(2)));
824        let w = width + delta.min(0.0);
825        let h = height - delta.max(0.0);
826
827        let recip_exponent = exponent.recip();
828        let scale = 0.5 * compute_erf7(std_dev_inv * 0.5 * (w.max(h) - 0.5 * radius));
829
830        let encoded = EncodedBlurredRoundedRectangle {
831            exponent,
832            recip_exponent,
833            width,
834            height,
835            scale,
836            r1,
837            std_dev_inv,
838            min_edge,
839            color: PremulColor::from_alpha_color(self.color),
840            w,
841            h,
842            transform,
843            x_advance,
844            y_advance,
845        };
846
847        let idx = paints.len();
848        paints.push(encoded.into());
849
850        Paint::Indexed(IndexedPaint::new(idx))
851    }
852}
853
854/// Calculates the transform necessary to map the line spanned by points src1, src2 to
855/// the line spanned by dst1, dst2.
856///
857/// This creates a transformation that maps any line segment to any other line segment.
858/// For gradients, we use this to transform the gradient line to a standard form (0,0) → (1,0).
859///
860/// Copied from <https://github.com/linebender/tiny-skia/blob/68b198a7210a6bbf752b43d6bc4db62445730313/src/shaders/radial_gradient.rs#L182>
861fn ts_from_line_to_line(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Affine {
862    let unit_to_line1 = unit_to_line(src1, src2);
863    // Calculate the transform necessary to map line1 to the unit vector.
864    let line1_to_unit = unit_to_line1.inverse();
865    // Then map the unit vector to line2.
866    let unit_to_line2 = unit_to_line(dst1, dst2);
867
868    unit_to_line2 * line1_to_unit
869}
870
871/// Calculate the transform necessary to map the unit vector to the line spanned by the points
872/// `p1` and `p2`.
873fn unit_to_line(p0: Point, p1: Point) -> Affine {
874    Affine::new([
875        p1.y - p0.y,
876        p0.x - p1.x,
877        p1.x - p0.x,
878        p1.y - p0.y,
879        p0.x,
880        p0.y,
881    ])
882}
883
884/// A helper trait for converting a premultiplied f32 color to `Self`.
885pub trait FromF32Color: Sized + Debug + Copy + Clone {
886    /// The zero value.
887    const ZERO: Self;
888    /// Convert from a premultiplied f32 color to `Self`.
889    fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4];
890}
891
892impl FromF32Color for f32 {
893    const ZERO: Self = 0.0;
894
895    fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4] {
896        color.val
897    }
898}
899
900impl FromF32Color for u8 {
901    const ZERO: Self = 0;
902
903    fn from_f32<S: Simd>(mut color: f32x4<S>) -> [Self; 4] {
904        let simd = color.simd;
905        color = f32x4::splat(simd, 0.5).madd(color, f32x4::splat(simd, 255.0));
906
907        [
908            color[0] as Self,
909            color[1] as Self,
910            color[2] as Self,
911            color[3] as Self,
912        ]
913    }
914}
915
916/// A lookup table for sampled gradient values.
917#[derive(Debug)]
918pub struct GradientLut<T: FromF32Color> {
919    lut: Vec<[T; 4]>,
920    scale: f32,
921}
922
923impl<T: FromF32Color> GradientLut<T> {
924    /// Create a new lookup table.
925    fn new<S: Simd>(simd: S, ranges: &[GradientRange]) -> Self {
926        let lut_size = determine_lut_size(ranges);
927
928        // Add a bit of padding since we always process in blocks of 4, even though less might be
929        // needed.
930        let mut lut = vec![[T::ZERO, T::ZERO, T::ZERO, T::ZERO]; lut_size + 3];
931
932        // Calculate how many indices are covered by each range.
933        let ramps = {
934            let mut ramps = Vec::with_capacity(ranges.len());
935            let mut prev_idx = 0;
936
937            for range in ranges {
938                let max_idx = (range.x1 * lut_size as f32) as usize;
939
940                ramps.push((prev_idx..max_idx, range));
941                prev_idx = max_idx;
942            }
943
944            ramps
945        };
946
947        let scale = lut_size as f32 - 1.0;
948
949        let inv_lut_scale = f32x4::splat(simd, 1.0 / scale);
950        let add_factor = f32x4::from_slice(simd, &[0.0, 1.0, 2.0, 3.0]) * inv_lut_scale;
951
952        for (ramp_range, range) in ramps {
953            let biases = f32x16::block_splat(f32x4::from_slice(simd, &range.bias));
954            let scales = f32x16::block_splat(f32x4::from_slice(simd, &range.scale));
955
956            ramp_range.step_by(4).for_each(|idx| {
957                let t_vals = add_factor.madd(f32x4::splat(simd, idx as f32), inv_lut_scale);
958
959                let t_vals = element_wise_splat(simd, t_vals);
960
961                let mut result = biases.madd(scales, t_vals);
962                let alphas = result.splat_4th();
963                // Due to floating-point impreciseness, it can happen that
964                // values either become greater than 1 or the RGB channels
965                // become greater than the alpha channel. To prevent overflows
966                // in later parts of the pipeline, we need to take the minimum here.
967                result = result.min(1.0).min(alphas);
968                let (im1, im2) = simd.split_f32x16(result);
969                let (r1, r2) = simd.split_f32x8(im1);
970                let (r3, r4) = simd.split_f32x8(im2);
971
972                let lut = &mut lut[idx..][..4];
973                lut[0] = T::from_f32(r1);
974                lut[1] = T::from_f32(r2);
975                lut[2] = T::from_f32(r3);
976                lut[3] = T::from_f32(r4);
977            });
978        }
979
980        // Due to SIMD we worked in blocks of 4, so we need to truncate to the actual length.
981        lut.truncate(lut_size);
982
983        Self { lut, scale }
984    }
985
986    /// Get the sample value at a specific index.
987    #[inline(always)]
988    pub fn get(&self, idx: usize) -> [T; 4] {
989        self.lut[idx]
990    }
991
992    /// Return the raw array of gradient sample values.
993    #[inline(always)]
994    pub fn lut(&self) -> &[[T; 4]] {
995        &self.lut
996    }
997
998    /// Get the scale factor by which to scale the parametric value to
999    /// compute the correct lookup index.
1000    #[inline(always)]
1001    pub fn scale_factor(&self) -> f32 {
1002        self.scale
1003    }
1004}
1005
1006fn determine_lut_size(ranges: &[GradientRange]) -> usize {
1007    // Of course in theory we could still have a stop at 0.0001 in which case this resolution
1008    // wouldn't be enough, but for all intents and purposes this should be more than sufficient
1009    // for most real cases.
1010    const MAX_LEN: usize = 4096;
1011
1012    // Inspired by Blend2D.
1013    // By default:
1014    // 256 for 2 stops.
1015    // 512 for 3 stops.
1016    // 1024 for 4 or more stops.
1017    let stop_len = match ranges.len() {
1018        1 => 256,
1019        2 => 512,
1020        _ => 1024,
1021    };
1022
1023    // In case we have some tricky stops (for example 3 stops with 0.0, 0.001, 1.0), we might
1024    // increase the resolution.
1025    let mut last_x1 = 0.0;
1026    let mut min_size = 0;
1027
1028    for x1 in ranges.iter().map(|e| e.x1) {
1029        // For example, if the first stop is at 0.001, then we need a resolution of at least 1000
1030        // so that we can still safely capture the first stop.
1031        let res = ((1.0 / (x1 - last_x1)).ceil() as usize)
1032            .min(MAX_LEN)
1033            .next_power_of_two();
1034        min_size = min_size.max(res);
1035        last_x1 = x1;
1036    }
1037
1038    // Take the maximum of both, but don't exceed `MAX_LEN`.
1039    stop_len.max(min_size)
1040}
1041
1042mod private {
1043    #[expect(unnameable_types, reason = "Sealed trait pattern.")]
1044    pub trait Sealed {}
1045
1046    impl Sealed for super::Gradient {}
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051    use super::{EncodeExt, Gradient};
1052    use crate::color::DynamicColor;
1053    use crate::color::palette::css::{BLACK, BLUE, GREEN};
1054    use crate::kurbo::{Affine, Point};
1055    use crate::peniko::{ColorStop, ColorStops, GradientKind};
1056    use alloc::vec;
1057    use smallvec::smallvec;
1058
1059    #[test]
1060    fn gradient_missing_stops() {
1061        let mut buf = vec![];
1062
1063        let gradient = Gradient {
1064            kind: GradientKind::Linear {
1065                start: Point::new(0.0, 0.0),
1066                end: Point::new(20.0, 0.0),
1067            },
1068            ..Default::default()
1069        };
1070
1071        assert_eq!(
1072            gradient.encode_into(&mut buf, Affine::IDENTITY),
1073            BLACK.into()
1074        );
1075    }
1076
1077    #[test]
1078    fn gradient_one_stop() {
1079        let mut buf = vec![];
1080
1081        let gradient = Gradient {
1082            kind: GradientKind::Linear {
1083                start: Point::new(0.0, 0.0),
1084                end: Point::new(20.0, 0.0),
1085            },
1086            stops: ColorStops(smallvec![ColorStop {
1087                offset: 0.0,
1088                color: DynamicColor::from_alpha_color(GREEN),
1089            }]),
1090            ..Default::default()
1091        };
1092
1093        // Should return the color of the first stop.
1094        assert_eq!(
1095            gradient.encode_into(&mut buf, Affine::IDENTITY),
1096            GREEN.into()
1097        );
1098    }
1099
1100    #[test]
1101    fn gradient_not_sorted_stops() {
1102        let mut buf = vec![];
1103
1104        let gradient = Gradient {
1105            kind: GradientKind::Linear {
1106                start: Point::new(0.0, 0.0),
1107                end: Point::new(20.0, 0.0),
1108            },
1109            stops: ColorStops(smallvec![
1110                ColorStop {
1111                    offset: 1.0,
1112                    color: DynamicColor::from_alpha_color(GREEN),
1113                },
1114                ColorStop {
1115                    offset: 0.0,
1116                    color: DynamicColor::from_alpha_color(BLUE),
1117                },
1118            ]),
1119            ..Default::default()
1120        };
1121
1122        assert_eq!(
1123            gradient.encode_into(&mut buf, Affine::IDENTITY),
1124            GREEN.into()
1125        );
1126    }
1127
1128    #[test]
1129    fn gradient_linear_degenerate() {
1130        let mut buf = vec![];
1131
1132        let gradient = Gradient {
1133            kind: GradientKind::Linear {
1134                start: Point::new(0.0, 0.0),
1135                end: Point::new(0.0, 0.0),
1136            },
1137            stops: ColorStops(smallvec![
1138                ColorStop {
1139                    offset: 0.0,
1140                    color: DynamicColor::from_alpha_color(GREEN),
1141                },
1142                ColorStop {
1143                    offset: 1.0,
1144                    color: DynamicColor::from_alpha_color(BLUE),
1145                },
1146            ]),
1147            ..Default::default()
1148        };
1149
1150        assert_eq!(
1151            gradient.encode_into(&mut buf, Affine::IDENTITY),
1152            GREEN.into()
1153        );
1154    }
1155
1156    #[test]
1157    fn gradient_radial_degenerate() {
1158        let mut buf = vec![];
1159
1160        let gradient = Gradient {
1161            kind: GradientKind::Radial {
1162                start_center: Point::new(0.0, 0.0),
1163                start_radius: 20.0,
1164                end_center: Point::new(0.0, 0.0),
1165                end_radius: 20.0,
1166            },
1167            stops: ColorStops(smallvec![
1168                ColorStop {
1169                    offset: 0.0,
1170                    color: DynamicColor::from_alpha_color(GREEN),
1171                },
1172                ColorStop {
1173                    offset: 1.0,
1174                    color: DynamicColor::from_alpha_color(BLUE),
1175                },
1176            ]),
1177            ..Default::default()
1178        };
1179
1180        assert_eq!(
1181            gradient.encode_into(&mut buf, Affine::IDENTITY),
1182            GREEN.into()
1183        );
1184    }
1185}