Skip to main content

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