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