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