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