Merge branch 'main' into strip_improvements
diff --git a/sparse_strips/vello_common/src/encode.rs b/sparse_strips/vello_common/src/encode.rs
index 715e79d..df558f3 100644
--- a/sparse_strips/vello_common/src/encode.rs
+++ b/sparse_strips/vello_common/src/encode.rs
@@ -11,14 +11,18 @@
 use crate::paint::{Image, ImageSource, IndexedPaint, Paint, PremulColor};
 use crate::peniko::{ColorStop, Extend, Gradient, GradientKind, ImageQuality};
 use alloc::borrow::Cow;
+use alloc::fmt::Debug;
+use alloc::vec;
 use alloc::vec::Vec;
 #[cfg(not(feature = "multithreading"))]
 use core::cell::OnceCell;
+use fearless_simd::{Simd, SimdBase, SimdFloat, f32x4, f32x16};
 use smallvec::SmallVec;
 // So we can just use `OnceCell` regardless of which feature is activated.
 #[cfg(feature = "multithreading")]
 use std::sync::OnceLock as OnceCell;
 
+use crate::simd::{Splat4thExt, element_wise_splat};
 #[cfg(not(feature = "std"))]
 use peniko::kurbo::common::FloatFuncs as _;
 
@@ -332,29 +336,12 @@
     cs: ColorSpaceTag,
     hue_dir: HueDirection,
 ) -> Vec<GradientRange> {
+    #[derive(Debug)]
     struct EncodedColorStop {
         offset: f32,
         color: crate::color::PremulColor<Srgb>,
     }
 
-    // Create additional (SRGB-encoded) stops in-between to approximate the color space we want to
-    // interpolate in.
-    let interpolated_stops = stops
-        .windows(2)
-        .flat_map(|s| {
-            let left_stop = &s[0];
-            let right_stop = &s[1];
-
-            let interpolated =
-                gradient::<Srgb>(left_stop.color, right_stop.color, cs, hue_dir, 0.01);
-
-            interpolated.map(|st| EncodedColorStop {
-                offset: left_stop.offset + (right_stop.offset - left_stop.offset) * st.0,
-                color: st.1,
-            })
-        })
-        .collect::<Vec<_>>();
-
     let create_range = |left_stop: &EncodedColorStop, right_stop: &EncodedColorStop| {
         let clamp = |mut color: [f32; 4]| {
             // The linear approximation of the gradient can produce values slightly outside of
@@ -388,15 +375,52 @@
         GradientRange { x1, bias, scale }
     };
 
-    interpolated_stops
-        .windows(2)
-        .map(|s| {
-            let left_stop = &s[0];
-            let right_stop = &s[1];
+    // Create additional (SRGB-encoded) stops in-between to approximate the color space we want to
+    // interpolate in.
+    if cs != ColorSpaceTag::Srgb {
+        let interpolated_stops = stops
+            .windows(2)
+            .flat_map(|s| {
+                let left_stop = &s[0];
+                let right_stop = &s[1];
 
-            create_range(left_stop, right_stop)
-        })
-        .collect()
+                let interpolated =
+                    gradient::<Srgb>(left_stop.color, right_stop.color, cs, hue_dir, 0.01);
+
+                interpolated.map(|st| EncodedColorStop {
+                    offset: left_stop.offset + (right_stop.offset - left_stop.offset) * st.0,
+                    color: st.1,
+                })
+            })
+            .collect::<Vec<_>>();
+
+        interpolated_stops
+            .windows(2)
+            .map(|s| {
+                let left_stop = &s[0];
+                let right_stop = &s[1];
+
+                create_range(left_stop, right_stop)
+            })
+            .collect()
+    } else {
+        stops
+            .windows(2)
+            .map(|c| {
+                let c0 = EncodedColorStop {
+                    offset: c[0].offset,
+                    color: c[0].color.to_alpha_color::<Srgb>().premultiply(),
+                };
+
+                let c1 = EncodedColorStop {
+                    offset: c[1].offset,
+                    color: c[1].color.to_alpha_color::<Srgb>().premultiply(),
+                };
+
+                create_range(&c0, &c1)
+            })
+            .collect()
+    }
 }
 
 pub(crate) fn x_y_advances(transform: &Affine) -> (Vec2, Vec2) {
@@ -679,13 +703,15 @@
 
 impl EncodedGradient {
     /// Get the lookup table for sampling u8-based gradient values.
-    pub fn u8_lut(&self) -> &GradientLut<u8> {
-        self.u8_lut.get_or_init(|| GradientLut::new(&self.ranges))
+    pub fn u8_lut<S: Simd>(&self, simd: S) -> &GradientLut<u8> {
+        self.u8_lut
+            .get_or_init(|| GradientLut::new(simd, &self.ranges))
     }
 
     /// Get the lookup table for sampling f32-based gradient values.
-    pub fn f32_lut(&self) -> &GradientLut<f32> {
-        self.f32_lut.get_or_init(|| GradientLut::new(&self.ranges))
+    pub fn f32_lut<S: Simd>(&self, simd: S) -> &GradientLut<f32> {
+        self.f32_lut
+            .get_or_init(|| GradientLut::new(simd, &self.ranges))
     }
 }
 
@@ -840,69 +866,106 @@
 }
 
 /// A helper trait for converting a premultiplied f32 color to `Self`.
-pub trait FromF32Color: Sized {
+pub trait FromF32Color: Sized + Debug + Copy + Clone {
+    /// The zero value.
+    const ZERO: Self;
     /// Convert from a premultiplied f32 color to `Self`.
-    fn from_f32(color: &[f32; 4]) -> [Self; 4];
+    fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4];
 }
 
 impl FromF32Color for f32 {
-    fn from_f32(color: &[f32; 4]) -> [Self; 4] {
-        *color
+    const ZERO: Self = 0.0;
+
+    fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4] {
+        color.val
     }
 }
 
 impl FromF32Color for u8 {
-    fn from_f32(color: &[f32; 4]) -> [Self; 4] {
+    const ZERO: Self = 0;
+
+    fn from_f32<S: Simd>(mut color: f32x4<S>) -> [Self; 4] {
+        let simd = color.simd;
+        color = f32x4::splat(simd, 0.5).madd(color, f32x4::splat(simd, 255.0));
+
         [
-            (color[0] * 255.0 + 0.5) as Self,
-            (color[1] * 255.0 + 0.5) as Self,
-            (color[2] * 255.0 + 0.5) as Self,
-            (color[3] * 255.0 + 0.5) as Self,
+            color[0] as Self,
+            color[1] as Self,
+            color[2] as Self,
+            color[3] as Self,
         ]
     }
 }
 
 /// A lookup table for sampled gradient values.
 #[derive(Debug)]
-pub struct GradientLut<T: Copy + Clone + FromF32Color> {
+pub struct GradientLut<T: FromF32Color> {
     lut: Vec<[T; 4]>,
     scale: f32,
 }
 
-impl<T: Copy + Clone + FromF32Color> GradientLut<T> {
+impl<T: FromF32Color> GradientLut<T> {
     /// Create a new lookup table.
-    fn new(ranges: &[GradientRange]) -> Self {
-        // TODO: SIMDify
+    fn new<S: Simd>(simd: S, ranges: &[GradientRange]) -> Self {
+        // Inspired by Blend2D.
+        let lut_size = match ranges.len() {
+            1 => 256,
+            2 => 512,
+            _ => 1024,
+        };
 
-        // Somewhat arbitrary, but we use 1024 samples for
-        // more than 2 stops, and 512 for just 2 stops. Blend2D does
-        // something similar.
-        let lut_size = if ranges.len() > 1 { 1024 } else { 512 };
+        // Add a bit of padding since we always process in blocks of 4, even though less might be
+        // needed.
+        let mut lut = vec![[T::ZERO, T::ZERO, T::ZERO, T::ZERO]; lut_size + 3];
 
-        let mut lut = Vec::with_capacity(lut_size);
+        // Calculate how many indices are covered by each range.
+        let ramps = {
+            let mut ramps = Vec::with_capacity(ranges.len());
+            let mut prev_idx = 0;
 
-        let inv_lut_size = 1.0 / lut_size as f32;
+            for range in ranges {
+                let max_idx = (range.x1 * lut_size as f32) as usize;
 
-        let mut cur_idx = 0;
-
-        (0..lut_size).for_each(|idx| {
-            let t_val = idx as f32 * inv_lut_size;
-
-            while ranges[cur_idx].x1 < t_val {
-                cur_idx += 1;
+                ramps.push((prev_idx..max_idx, range));
+                prev_idx = max_idx;
             }
 
-            let range = &ranges[cur_idx];
-            let mut interpolated = [0.0_f32; 4];
+            ramps
+        };
 
-            let bias = range.bias;
+        let inv_lut_size = f32x4::splat(simd, 1.0 / lut_size as f32);
+        let add_factor = f32x4::from_slice(simd, &[0.0, 1.0, 2.0, 3.0]) * inv_lut_size;
 
-            for (comp_idx, comp) in interpolated.iter_mut().enumerate() {
-                *comp = bias[comp_idx] + range.scale[comp_idx] * t_val;
-            }
+        for (ramp_range, range) in ramps {
+            let biases = f32x16::block_splat(f32x4::from_slice(simd, &range.bias));
+            let scales = f32x16::block_splat(f32x4::from_slice(simd, &range.scale));
 
-            lut.push(T::from_f32(&interpolated));
-        });
+            ramp_range.step_by(4).for_each(|idx| {
+                let t_vals = add_factor.madd(f32x4::splat(simd, idx as f32), inv_lut_size);
+
+                let t_vals = element_wise_splat(simd, t_vals);
+
+                let mut result = biases.madd(scales, t_vals);
+                let alphas = result.splat_4th();
+                // Due to floating-point impreciseness, it can happen that
+                // values either become greater than 1 or the RGB channels
+                // become greater than the alpha channel. To prevent overflows
+                // in later parts of the pipeline, we need to take the minimum here.
+                result = result.min(1.0).min(alphas);
+                let (im1, im2) = simd.split_f32x16(result);
+                let (r1, r2) = simd.split_f32x8(im1);
+                let (r3, r4) = simd.split_f32x8(im2);
+
+                let lut = &mut lut[idx..][..4];
+                lut[0] = T::from_f32(r1);
+                lut[1] = T::from_f32(r2);
+                lut[2] = T::from_f32(r3);
+                lut[3] = T::from_f32(r4);
+            });
+        }
+
+        // Due to SIMD we worked in blocks of 4, so we need to truncate to the actual length.
+        lut.truncate(lut_size);
 
         let scale = lut.len() as f32 - 1.0;
 
diff --git a/sparse_strips/vello_common/src/lib.rs b/sparse_strips/vello_common/src/lib.rs
index 2f2575f..0032839 100644
--- a/sparse_strips/vello_common/src/lib.rs
+++ b/sparse_strips/vello_common/src/lib.rs
@@ -77,6 +77,7 @@
 #[cfg(feature = "pico_svg")]
 pub mod pico_svg;
 pub mod pixmap;
+pub mod simd;
 pub mod strip;
 pub mod tile;
 mod util;
diff --git a/sparse_strips/vello_common/src/simd.rs b/sparse_strips/vello_common/src/simd.rs
new file mode 100644
index 0000000..7f92005
--- /dev/null
+++ b/sparse_strips/vello_common/src/simd.rs
@@ -0,0 +1,98 @@
+// Copyright 2025 the Vello Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+//! A number of SIMD extension traits.
+
+use fearless_simd::*;
+
+/// Splatting every 4th element in the vector, used for splatting the alpha value of
+/// a color to all lanes.
+pub trait Splat4thExt<S> {
+    /// Splat every 4th element of the vector.
+    fn splat_4th(self) -> Self;
+}
+
+impl<S: Simd> Splat4thExt<S> for f32x4<S> {
+    #[inline(always)]
+    fn splat_4th(self) -> Self {
+        // TODO: Explore whether it's just faster to manually access the 4th element and splat it.
+        let zip1 = self.zip_high(self);
+        zip1.zip_high(zip1)
+    }
+}
+
+impl<S: Simd> Splat4thExt<S> for f32x8<S> {
+    #[inline(always)]
+    fn splat_4th(self) -> Self {
+        let (mut p1, mut p2) = self.simd.split_f32x8(self);
+        p1 = p1.splat_4th();
+        p2 = p2.splat_4th();
+
+        self.simd.combine_f32x4(p1, p2)
+    }
+}
+
+impl<S: Simd> Splat4thExt<S> for f32x16<S> {
+    #[inline(always)]
+    fn splat_4th(self) -> Self {
+        let (mut p1, mut p2) = self.simd.split_f32x16(self);
+        p1 = p1.splat_4th();
+        p2 = p2.splat_4th();
+
+        self.simd.combine_f32x8(p1, p2)
+    }
+}
+
+impl<S: Simd> Splat4thExt<S> for u8x16<S> {
+    #[inline(always)]
+    fn splat_4th(self) -> Self {
+        // TODO: SIMDify
+        Self {
+            val: [
+                self.val[3],
+                self.val[3],
+                self.val[3],
+                self.val[3],
+                self.val[7],
+                self.val[7],
+                self.val[7],
+                self.val[7],
+                self.val[11],
+                self.val[11],
+                self.val[11],
+                self.val[11],
+                self.val[15],
+                self.val[15],
+                self.val[15],
+                self.val[15],
+            ],
+            simd: self.simd,
+        }
+    }
+}
+
+impl<S: Simd> Splat4thExt<S> for u8x32<S> {
+    #[inline(always)]
+    fn splat_4th(self) -> Self {
+        let (mut p1, mut p2) = self.simd.split_u8x32(self);
+        p1 = p1.splat_4th();
+        p2 = p2.splat_4th();
+
+        self.simd.combine_u8x16(p1, p2)
+    }
+}
+
+/// Splat each single element in the vector to 4 lanes.
+#[inline(always)]
+pub fn element_wise_splat<S: Simd>(simd: S, input: f32x4<S>) -> f32x16<S> {
+    simd.combine_f32x8(
+        simd.combine_f32x4(
+            f32x4::splat(simd, input.val[0]),
+            f32x4::splat(simd, input.val[1]),
+        ),
+        simd.combine_f32x4(
+            f32x4::splat(simd, input.val[2]),
+            f32x4::splat(simd, input.val[3]),
+        ),
+    )
+}
diff --git a/sparse_strips/vello_cpu/src/fine/common/gradient/mod.rs b/sparse_strips/vello_cpu/src/fine/common/gradient/mod.rs
index 42183dc..a39e6e5 100644
--- a/sparse_strips/vello_cpu/src/fine/common/gradient/mod.rs
+++ b/sparse_strips/vello_cpu/src/fine/common/gradient/mod.rs
@@ -50,7 +50,7 @@
         has_undefined: bool,
         t_vals: &'a [f32],
     ) -> Self {
-        let lut = gradient.f32_lut();
+        let lut = gradient.f32_lut(simd);
         let scale_factor = f32x8::splat(simd, lut.scale_factor());
 
         Self {
diff --git a/sparse_strips/vello_cpu/src/fine/common/image.rs b/sparse_strips/vello_cpu/src/fine/common/image.rs
index 39bc6ad..b61c9f1 100644
--- a/sparse_strips/vello_cpu/src/fine/common/image.rs
+++ b/sparse_strips/vello_cpu/src/fine/common/image.rs
@@ -1,7 +1,6 @@
 // Copyright 2025 the Vello Authors
 // SPDX-License-Identifier: Apache-2.0 OR MIT
 
-use crate::fine::highp::element_wise_splat;
 use crate::fine::macros::{f32x16_painter, u8x16_painter};
 use crate::fine::{PosExt, Splat4thExt, u8_to_f32};
 use crate::kurbo::Point;
@@ -9,6 +8,7 @@
 use vello_common::encode::EncodedImage;
 use vello_common::fearless_simd::{Bytes, Simd, SimdBase, SimdFloat, f32x4, f32x16, u8x16, u32x4};
 use vello_common::pixmap::Pixmap;
+use vello_common::simd::element_wise_splat;
 
 /// A painter for nearest-neighbor images with no skewing.
 #[derive(Debug)]
diff --git a/sparse_strips/vello_cpu/src/fine/highp/mod.rs b/sparse_strips/vello_cpu/src/fine/highp/mod.rs
index 9ac35a7..557900e 100644
--- a/sparse_strips/vello_cpu/src/fine/highp/mod.rs
+++ b/sparse_strips/vello_cpu/src/fine/highp/mod.rs
@@ -2,12 +2,9 @@
 // SPDX-License-Identifier: Apache-2.0 OR MIT
 
 use crate::fine::FineKernel;
-use crate::fine::common::gradient::GradientPainter;
 use crate::fine::{COLOR_COMPONENTS, Painter};
 use crate::peniko::BlendMode;
 use crate::region::Region;
-use alloc::boxed::Box;
-use vello_common::encode::EncodedGradient;
 use vello_common::fearless_simd::*;
 use vello_common::paint::PremulColor;
 use vello_common::tile::Tile;
@@ -61,15 +58,6 @@
         }
     }
 
-    fn gradient_painter<'a>(
-        simd: S,
-        gradient: &'a EncodedGradient,
-        has_undefined: bool,
-        t_vals: &'a [f32],
-    ) -> Box<dyn Painter + 'a> {
-        Box::new(GradientPainter::new(simd, gradient, has_undefined, t_vals))
-    }
-
     fn apply_mask(
         simd: S,
         dest: &mut [Self::Numeric],
@@ -83,7 +71,7 @@
     }
 
     #[inline(always)]
-    fn apply_painter<'a>(_: S, dest: &mut [Self::Numeric], mut painter: Box<dyn Painter + 'a>) {
+    fn apply_painter<'a>(_: S, dest: &mut [Self::Numeric], mut painter: impl Painter + 'a) {
         painter.paint_f32(dest);
     }
 
@@ -280,20 +268,6 @@
 }
 
 #[inline(always)]
-pub(crate) fn element_wise_splat<S: Simd>(simd: S, input: f32x4<S>) -> f32x16<S> {
-    simd.combine_f32x8(
-        simd.combine_f32x4(
-            f32x4::splat(simd, input.val[0]),
-            f32x4::splat(simd, input.val[1]),
-        ),
-        simd.combine_f32x4(
-            f32x4::splat(simd, input.val[2]),
-            f32x4::splat(simd, input.val[3]),
-        ),
-    )
-}
-
-#[inline(always)]
 fn extract_masks<S: Simd>(simd: S, masks: &[u8]) -> f32x16<S> {
     let mut base_mask = [
         masks[0] as f32,
diff --git a/sparse_strips/vello_cpu/src/fine/lowp/gradient.rs b/sparse_strips/vello_cpu/src/fine/lowp/gradient.rs
index f587739..f71320d 100644
--- a/sparse_strips/vello_cpu/src/fine/lowp/gradient.rs
+++ b/sparse_strips/vello_cpu/src/fine/lowp/gradient.rs
@@ -19,7 +19,7 @@
 
 impl<'a, S: Simd> GradientPainter<'a, S> {
     pub(crate) fn new(simd: S, gradient: &'a EncodedGradient, t_vals: &'a [f32]) -> Self {
-        let lut = gradient.u8_lut();
+        let lut = gradient.u8_lut(simd);
         let scale_factor = f32x16::splat(simd, lut.scale_factor());
 
         Self {
diff --git a/sparse_strips/vello_cpu/src/fine/lowp/image.rs b/sparse_strips/vello_cpu/src/fine/lowp/image.rs
index 9a555f6..8a61eaa 100644
--- a/sparse_strips/vello_cpu/src/fine/lowp/image.rs
+++ b/sparse_strips/vello_cpu/src/fine/lowp/image.rs
@@ -2,12 +2,12 @@
 // SPDX-License-Identifier: Apache-2.0 OR MIT
 
 use crate::fine::common::image::{ImagePainterData, extend, sample};
-use crate::fine::highp::element_wise_splat;
 use crate::fine::macros::u8x16_painter;
 use crate::fine::{PosExt, f32_to_u8};
 use vello_common::encode::EncodedImage;
 use vello_common::fearless_simd::{Simd, SimdBase, f32x4, u8x16};
 use vello_common::pixmap::Pixmap;
+use vello_common::simd::element_wise_splat;
 
 /// A faster bilinear image renderer for the u8 pipeline.
 #[derive(Debug)]
diff --git a/sparse_strips/vello_cpu/src/fine/lowp/mod.rs b/sparse_strips/vello_cpu/src/fine/lowp/mod.rs
index 26e0ef9..bdb045b 100644
--- a/sparse_strips/vello_cpu/src/fine/lowp/mod.rs
+++ b/sparse_strips/vello_cpu/src/fine/lowp/mod.rs
@@ -5,21 +5,17 @@
 mod gradient;
 mod image;
 
-use crate::fine::common;
-use crate::fine::common::image::FilteredImagePainter;
 use crate::fine::lowp::image::BilinearImagePainter;
 use crate::fine::{COLOR_COMPONENTS, Painter, SCRATCH_BUF_SIZE};
 use crate::fine::{FineKernel, f32_to_u8, highp, u8_to_f32};
 use crate::peniko::BlendMode;
 use crate::region::Region;
 use crate::util::Div255Ext;
-use alloc::boxed::Box;
 use bytemuck::cast_slice;
 use vello_common::coarse::WideTile;
 use vello_common::encode::{EncodedGradient, EncodedImage};
 use vello_common::fearless_simd::*;
 use vello_common::paint::PremulColor;
-use vello_common::peniko::ImageQuality;
 use vello_common::pixmap::Pixmap;
 use vello_common::tile::Tile;
 
@@ -58,37 +54,19 @@
     fn gradient_painter<'a>(
         simd: S,
         gradient: &'a EncodedGradient,
-        has_undefined: bool,
         t_vals: &'a [f32],
-    ) -> Box<dyn Painter + 'a> {
-        if has_undefined {
-            Box::new(common::gradient::GradientPainter::new(
-                simd,
-                gradient,
-                has_undefined,
-                t_vals,
-            ))
-        } else {
-            Box::new(gradient::GradientPainter::new(simd, gradient, t_vals))
-        }
+    ) -> impl Painter + 'a {
+        gradient::GradientPainter::new(simd, gradient, t_vals)
     }
 
-    fn filtered_image_painter<'a>(
+    fn medium_quality_image_painter<'a>(
         simd: S,
         image: &'a EncodedImage,
         pixmap: &'a Pixmap,
         start_x: u16,
         start_y: u16,
-    ) -> Box<dyn Painter + 'a> {
-        if image.quality == ImageQuality::Medium {
-            Box::new(BilinearImagePainter::new(
-                simd, image, pixmap, start_x, start_y,
-            ))
-        } else {
-            Box::new(FilteredImagePainter::new(
-                simd, image, pixmap, start_x, start_y,
-            ))
-        }
+    ) -> impl Painter + 'a {
+        BilinearImagePainter::new(simd, image, pixmap, start_x, start_y)
     }
 
     fn apply_mask(
@@ -106,7 +84,7 @@
     }
 
     #[inline(always)]
-    fn apply_painter<'a>(_: S, dest: &mut [Self::Numeric], mut painter: Box<dyn Painter + 'a>) {
+    fn apply_painter<'a>(_: S, dest: &mut [Self::Numeric], mut painter: impl Painter + 'a) {
         painter.paint_u8(dest);
     }
 
diff --git a/sparse_strips/vello_cpu/src/fine/mod.rs b/sparse_strips/vello_cpu/src/fine/mod.rs
index 6991517..9feb999 100644
--- a/sparse_strips/vello_cpu/src/fine/mod.rs
+++ b/sparse_strips/vello_cpu/src/fine/mod.rs
@@ -5,9 +5,8 @@
 mod highp;
 mod lowp;
 
-use crate::peniko::{BlendMode, Compose, Mix};
+use crate::peniko::{BlendMode, Compose, ImageQuality, Mix};
 use crate::region::Region;
-use alloc::boxed::Box;
 use alloc::vec;
 use alloc::vec::Vec;
 use core::fmt::Debug;
@@ -24,10 +23,10 @@
 pub const SCRATCH_BUF_SIZE: usize =
     WideTile::WIDTH as usize * Tile::HEIGHT as usize * COLOR_COMPONENTS;
 
-use crate::fine::common::gradient::calculate_t_vals;
 use crate::fine::common::gradient::linear::SimdLinearKind;
 use crate::fine::common::gradient::radial::SimdRadialKind;
 use crate::fine::common::gradient::sweep::SimdSweepKind;
+use crate::fine::common::gradient::{GradientPainter, calculate_t_vals};
 use crate::fine::common::image::{FilteredImagePainter, NNImagePainter, PlainNNImagePainter};
 use crate::fine::common::rounded_blurred_rect::BlurredRoundedRectFiller;
 use crate::util::{BlendModeExt, EncodedImageExt};
@@ -37,6 +36,7 @@
     Simd, SimdBase, SimdFloat, SimdInto, f32x4, f32x8, f32x16, u8x16, u8x32, u32x4, u32x8,
 };
 use vello_common::pixmap::Pixmap;
+use vello_common::simd::Splat4thExt;
 
 pub type ScratchBuf<F> = [F; SCRATCH_BUF_SIZE];
 
@@ -91,24 +91,29 @@
 
 #[inline(always)]
 pub(crate) fn f32_to_u8<S: Simd>(val: f32x16<S>) -> u8x16<S> {
-    // TODO: SIMDify
+    let simd = val.simd;
+    // Note that converting to u32 first using SIMD and then u8
+    // is much faster than converting directly from f32 to u8.
+    let converted = simd.cvt_u32_f32x16(val);
+
+    // TODO: Maybe we can also do this using SIMD?
     [
-        val.val[0] as u8,
-        val.val[1] as u8,
-        val.val[2] as u8,
-        val.val[3] as u8,
-        val.val[4] as u8,
-        val.val[5] as u8,
-        val.val[6] as u8,
-        val.val[7] as u8,
-        val.val[8] as u8,
-        val.val[9] as u8,
-        val.val[10] as u8,
-        val.val[11] as u8,
-        val.val[12] as u8,
-        val.val[13] as u8,
-        val.val[14] as u8,
-        val.val[15] as u8,
+        converted[0] as u8,
+        converted[1] as u8,
+        converted[2] as u8,
+        converted[3] as u8,
+        converted[4] as u8,
+        converted[5] as u8,
+        converted[6] as u8,
+        converted[7] as u8,
+        converted[8] as u8,
+        converted[9] as u8,
+        converted[10] as u8,
+        converted[11] as u8,
+        converted[12] as u8,
+        converted[13] as u8,
+        converted[14] as u8,
+        converted[15] as u8,
     ]
     .simd_into(val.simd)
 }
@@ -191,9 +196,18 @@
     fn gradient_painter<'a>(
         simd: S,
         gradient: &'a EncodedGradient,
-        has_undefined: bool,
         t_vals: &'a [f32],
-    ) -> Box<dyn Painter + 'a>;
+    ) -> impl Painter + 'a {
+        GradientPainter::new(simd, gradient, false, t_vals)
+    }
+    /// Return the painter used for painting gradients, with support for masking undefined locations.
+    fn gradient_painter_with_undefined<'a>(
+        simd: S,
+        gradient: &'a EncodedGradient,
+        t_vals: &'a [f32],
+    ) -> impl Painter + 'a {
+        GradientPainter::new(simd, gradient, true, t_vals)
+    }
     /// Return the painter used for painting plain nearest-neighbor images.
     ///
     /// Plain nearest-neighbor images are images with the quality 'Low' and no skewing component in their
@@ -204,10 +218,8 @@
         pixmap: &'a Pixmap,
         start_x: u16,
         start_y: u16,
-    ) -> Box<dyn Painter + 'a> {
-        Box::new(PlainNNImagePainter::new(
-            simd, image, pixmap, start_x, start_y,
-        ))
+    ) -> impl Painter + 'a {
+        PlainNNImagePainter::new(simd, image, pixmap, start_x, start_y)
     }
     /// Return the painter used for painting plain nearest-neighbor images.
     ///
@@ -218,20 +230,28 @@
         pixmap: &'a Pixmap,
         start_x: u16,
         start_y: u16,
-    ) -> Box<dyn Painter + 'a> {
-        Box::new(NNImagePainter::new(simd, image, pixmap, start_x, start_y))
+    ) -> impl Painter + 'a {
+        NNImagePainter::new(simd, image, pixmap, start_x, start_y)
     }
-    /// Return the painter used for painting image with `Medium` or `High` quality.
-    fn filtered_image_painter<'a>(
+    /// Return the painter used for painting image with `Medium` quality.
+    fn medium_quality_image_painter<'a>(
         simd: S,
         image: &'a EncodedImage,
         pixmap: &'a Pixmap,
         start_x: u16,
         start_y: u16,
-    ) -> Box<dyn Painter + 'a> {
-        Box::new(FilteredImagePainter::new(
-            simd, image, pixmap, start_x, start_y,
-        ))
+    ) -> impl Painter + 'a {
+        FilteredImagePainter::new(simd, image, pixmap, start_x, start_y)
+    }
+    /// Return the painter used for painting image with `High` quality.
+    fn high_quality_image_painter<'a>(
+        simd: S,
+        image: &'a EncodedImage,
+        pixmap: &'a Pixmap,
+        start_x: u16,
+        start_y: u16,
+    ) -> impl Painter + 'a {
+        FilteredImagePainter::new(simd, image, pixmap, start_x, start_y)
     }
     /// Return the painter used for painting blurred rounded rectangles.
     fn blurred_rounded_rectangle_painter<'a>(
@@ -239,13 +259,13 @@
         rect: &'a EncodedBlurredRoundedRectangle,
         start_x: u16,
         start_y: u16,
-    ) -> Box<dyn Painter + 'a> {
-        Box::new(BlurredRoundedRectFiller::new(simd, rect, start_x, start_y))
+    ) -> impl Painter + 'a {
+        BlurredRoundedRectFiller::new(simd, rect, start_x, start_y)
     }
     /// Apply the mask to the destination buffer.
     fn apply_mask(simd: S, dest: &mut [Self::Numeric], src: impl Iterator<Item = Self::NumericVec>);
     /// Apply the painter to the destination buffer.
-    fn apply_painter<'a>(simd: S, dest: &mut [Self::Numeric], painter: Box<dyn Painter + 'a>);
+    fn apply_painter<'a>(simd: S, dest: &mut [Self::Numeric], painter: impl Painter + 'a);
     /// Do basic alpha compositing with a solid color.
     fn alpha_composite_solid(
         simd: S,
@@ -513,7 +533,7 @@
 
                                 fill_complex_paint!(
                                     g.has_opacities,
-                                    T::gradient_painter(self.simd, g, false, f32_buf)
+                                    T::gradient_painter(self.simd, g, f32_buf)
                                 );
                             }
                             EncodedKind::Sweep(s) => {
@@ -528,7 +548,7 @@
 
                                 fill_complex_paint!(
                                     g.has_opacities,
-                                    T::gradient_painter(self.simd, g, false, f32_buf)
+                                    T::gradient_painter(self.simd, g, f32_buf)
                                 );
                             }
                             EncodedKind::Radial(r) => {
@@ -541,10 +561,17 @@
                                     start_y,
                                 );
 
-                                fill_complex_paint!(
-                                    g.has_opacities,
-                                    T::gradient_painter(self.simd, g, r.has_undefined(), f32_buf)
-                                );
+                                if r.has_undefined() {
+                                    fill_complex_paint!(
+                                        g.has_opacities,
+                                        T::gradient_painter_with_undefined(self.simd, g, f32_buf)
+                                    );
+                                } else {
+                                    fill_complex_paint!(
+                                        g.has_opacities,
+                                        T::gradient_painter(self.simd, g, f32_buf)
+                                    );
+                                }
                             }
                         }
                     }
@@ -555,12 +582,21 @@
 
                         match (i.has_skew(), i.nearest_neighbor()) {
                             (_, false) => {
-                                fill_complex_paint!(
-                                    i.has_opacities,
-                                    T::filtered_image_painter(
-                                        self.simd, i, pixmap, start_x, start_y
-                                    )
-                                );
+                                if i.quality == ImageQuality::Medium {
+                                    fill_complex_paint!(
+                                        i.has_opacities,
+                                        T::medium_quality_image_painter(
+                                            self.simd, i, pixmap, start_x, start_y
+                                        )
+                                    );
+                                } else {
+                                    fill_complex_paint!(
+                                        i.has_opacities,
+                                        T::high_quality_image_painter(
+                                            self.simd, i, pixmap, start_x, start_y
+                                        )
+                                    );
+                                }
                             }
                             (false, true) => {
                                 fill_complex_paint!(
@@ -587,15 +623,19 @@
         let (source_buffer, rest) = self.blend_buf.split_last_mut().unwrap();
         let target_buffer = rest.last_mut().unwrap();
 
-        T::blend(
-            self.simd,
-            target_buffer,
-            source_buffer
-                .chunks_exact(T::Composite::LENGTH)
-                .map(|s| T::Composite::from_slice(self.simd, s)),
-            blend_mode,
-            None,
-        );
+        if blend_mode.is_default() {
+            T::alpha_composite_buffer(self.simd, target_buffer, source_buffer, None);
+        } else {
+            T::blend(
+                self.simd,
+                target_buffer,
+                source_buffer
+                    .chunks_exact(T::Composite::LENGTH)
+                    .map(|s| T::Composite::from_slice(self.simd, s)),
+                blend_mode,
+                None,
+            );
+        }
     }
 
     fn clip(&mut self, x: usize, width: usize, alphas: Option<&[u8]>) {
@@ -644,81 +684,6 @@
     }
 }
 
-/// Splatting every 4th element in the vector, used for splatting the alpha value of
-/// a color to all lanes.
-pub trait Splat4thExt<S> {
-    fn splat_4th(self) -> Self;
-}
-
-impl<S: Simd> Splat4thExt<S> for f32x4<S> {
-    #[inline(always)]
-    fn splat_4th(self) -> Self {
-        let zip1 = self.zip_high(self);
-        zip1.zip_high(zip1)
-    }
-}
-
-impl<S: Simd> Splat4thExt<S> for f32x8<S> {
-    #[inline(always)]
-    fn splat_4th(self) -> Self {
-        let (mut p1, mut p2) = self.simd.split_f32x8(self);
-        p1 = p1.splat_4th();
-        p2 = p2.splat_4th();
-
-        self.simd.combine_f32x4(p1, p2)
-    }
-}
-
-impl<S: Simd> Splat4thExt<S> for f32x16<S> {
-    #[inline(always)]
-    fn splat_4th(self) -> Self {
-        let (mut p1, mut p2) = self.simd.split_f32x16(self);
-        p1 = p1.splat_4th();
-        p2 = p2.splat_4th();
-
-        self.simd.combine_f32x8(p1, p2)
-    }
-}
-
-impl<S: Simd> Splat4thExt<S> for u8x16<S> {
-    #[inline(always)]
-    fn splat_4th(self) -> Self {
-        // TODO: SIMDify
-        Self {
-            val: [
-                self.val[3],
-                self.val[3],
-                self.val[3],
-                self.val[3],
-                self.val[7],
-                self.val[7],
-                self.val[7],
-                self.val[7],
-                self.val[11],
-                self.val[11],
-                self.val[11],
-                self.val[11],
-                self.val[15],
-                self.val[15],
-                self.val[15],
-                self.val[15],
-            ],
-            simd: self.simd,
-        }
-    }
-}
-
-impl<S: Simd> Splat4thExt<S> for u8x32<S> {
-    #[inline(always)]
-    fn splat_4th(self) -> Self {
-        let (mut p1, mut p2) = self.simd.split_u8x32(self);
-        p1 = p1.splat_4th();
-        p2 = p2.splat_4th();
-
-        self.simd.combine_u8x16(p1, p2)
-    }
-}
-
 /// The results of an f32 shader, where each channel stored separately.
 pub(crate) struct ShaderResultF32<S: Simd> {
     pub(crate) r: f32x8<S>,
diff --git a/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_noto.png b/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_noto.png
index 35eb53c..167770b 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_noto.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_noto.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:53e7b8bd0aec7a12ca51f232e8956e42ef0a0bf190cf0dc0edd2b05e241ee540
-size 9946
+oid sha256:fc3d054072d054284ee72eb4ab71c66d21f11dd72d8d8f3b881db41f2f31992e
+size 9903
diff --git a/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_test_glyphs.png b/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_test_glyphs.png
index 49bfbe4..5e65bbd 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_test_glyphs.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_test_glyphs.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:d45439545a4cb578d80ed9f69d2daad1b0404b91ee5ba2b7465ffbdfb2a431b0
-size 127633
+oid sha256:abe856ee4ff107fb268064518d8fd74575714c286b2079eafc6c5a5f13aed503
+size 127370
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_2_stops.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_2_stops.png
index b023591..cbcc9e4 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_2_stops.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_2_stops.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f97abbc5e58147f9a231c52e7ebb21d6765b6b6c3278569fbf28181a9f1ff279
-size 257
+oid sha256:a877bae5cc9af41bad3219e5300c56a331cbaac757485913236b1fa19d99af50
+size 245
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_2_stops_with_alpha.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_2_stops_with_alpha.png
index 78d6600..e10a574 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_2_stops_with_alpha.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_2_stops_with_alpha.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c9a97e0a76f74c318d0d6a928a34bb373ca22ceaf7c156df8ebf04be61b34e1e
-size 263
+oid sha256:67ccd25afd8f91572c400afee979222fff1a18db4304d793a032aa7dca75447b
+size 266
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_negative_direction.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_negative_direction.png
index 57d2662..b242c99 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_negative_direction.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_negative_direction.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:013e67184e021ec1792a8dbb0a227f2cf5f2dba7a2cc9e4df8820768691edd20
-size 258
+oid sha256:cfcee79b2d963755cce44f8995e793037014a3916d516fcb730644365909be35
+size 247
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_pad.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_pad.png
index 2324add..4017d77 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_pad.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_pad.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3876d67057ffa18030418eabeaff2697f1830bb86860fd6b55b883702e9553ca
-size 435
+oid sha256:14dccd7a9a993068f2062d107a9b5cfbc6d4c27be6f7b2e1a8fefae2d0971b9c
+size 453
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_reflect.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_reflect.png
index de10c62..b3b3207 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_reflect.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_reflect.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fbcd96d7e9c64dd61bce6c10a3576f0002324fd26b9fa8bbf41df2c9834bf5c1
+oid sha256:6c5155f52b5c70a000513883753a8deb09c13f90cd8597e9a46a574babeb36ea
 size 615
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_repeat.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_repeat.png
index a32d6a1..8232587 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_repeat.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_spread_method_repeat.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:aa9f2ea69723ae2e96845ccc310772d181d9041b623fbeedf68809bf0048d35d
+oid sha256:6a332600734a257b02fccc8fb3e94d87611e3a12ac75646fa0efd236a365f1e6
 size 415
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_vertical.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_vertical.png
index 83d2925..2085d3a 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_vertical.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_vertical.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bcb2361ba91ec72b20aa2455ab337d9eac2f8d421c4c9690746c586ab6f33aed
-size 277
+oid sha256:6e83eea44562f0e330112e4f7625a1712e35f56e3128877480d2251cf6d260c9
+size 272
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_downward_y.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_downward_y.png
index bbb8223..04ea3bf 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_downward_y.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_downward_y.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e1f4ec72f27f676929bb777a45aabdc9de23b41359b5d294d97d9a077b908ecc
-size 461
+oid sha256:8ef89a7548774004759ba8969a90956b4f18e642abdb97541f8a96736fe32a4c
+size 533
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_identity.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_identity.png
index 70a8c19..f2e06b3 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_identity.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_identity.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5e35fc567139cae0b1b381b7d0a28cb778f3563570e1ae18316b3e47b2d925b9
+oid sha256:c11042007db24f708df578043488e21e02a16ea3cac0230cb17a3cdb05e86eb1
 size 438
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_negative_scale.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_negative_scale.png
index cc4224e..100ac0d 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_negative_scale.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_negative_scale.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:14cf16def2ef8f2650b7cf4936984ca38999c20a99acb2de74673f4a630cb402
+oid sha256:d6592fa516bc2a20e20bd23e30b424f93524d3c03d92e841d5830a312d6bc652
 size 444
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scale.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scale.png
index 70a8c19..f2e06b3 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scale.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scale.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5e35fc567139cae0b1b381b7d0a28cb778f3563570e1ae18316b3e47b2d925b9
+oid sha256:c11042007db24f708df578043488e21e02a16ea3cac0230cb17a3cdb05e86eb1
 size 438
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scale_and_translate.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scale_and_translate.png
index 70a8c19..f2e06b3 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scale_and_translate.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scale_and_translate.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5e35fc567139cae0b1b381b7d0a28cb778f3563570e1ae18316b3e47b2d925b9
+oid sha256:c11042007db24f708df578043488e21e02a16ea3cac0230cb17a3cdb05e86eb1
 size 438
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scaling_non_uniform.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scaling_non_uniform.png
index 7cd787f..65ec90b 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scaling_non_uniform.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_scaling_non_uniform.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e2d292ce2d2f1c9f5336601a9f3f89f848ae0098153067ab17309c063a74c6b8
-size 528
+oid sha256:0078167dd363a6d0bc21a00d8ab6f6755ff400f9cd5b678099afdd3e95061cf3
+size 529
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_translate.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_translate.png
index 70a8c19..f2e06b3 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_translate.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_transform_translate.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5e35fc567139cae0b1b381b7d0a28cb778f3563570e1ae18316b3e47b2d925b9
+oid sha256:c11042007db24f708df578043488e21e02a16ea3cac0230cb17a3cdb05e86eb1
 size 438
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_upward_y.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_upward_y.png
index b207a69..2fb15bf 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_upward_y.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_linear_with_upward_y.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3fc63cc58fea1d5769e2c2b7dcd28d179d52a7deef94cca78ca1f3602fa91065
-size 446
+oid sha256:1147e89ae951f7ead22beb24137a8ca41e4d42fb654253a230d97101a3f2ce57
+size 554
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_on_3_wide_tiles.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_on_3_wide_tiles.png
index 6406951..a39af52 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_on_3_wide_tiles.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_on_3_wide_tiles.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:e26a9adc3b46c70f85b6bb59eb984b4061812aae631fdfe049223349c1b27d58
-size 251
+oid sha256:c0052b1875e7d87f62535a1cdea8425693855f388a826a36b88cd6ec47d1e3a9
+size 923
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_2_stops.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_2_stops.png
index 5e34954..fb9ce98 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_2_stops.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_2_stops.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:aca1f00d80d17532e862c0999e6ce4fa19f4c6c719fbb2f72e9fc4a466aa92ed
-size 1586
+oid sha256:bec40b2a9a0e7ba6088e0ef7e0003ced3b2f1ccab0b31ec49237f134dd31bd40
+size 1562
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_2_stops_with_alpha.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_2_stops_with_alpha.png
index 28de26c..573374e 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_2_stops_with_alpha.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_2_stops_with_alpha.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:089cdc8f0e527412f0d27f7a918ea67040fb8bbe4016b67498f9fcb436107093
-size 2121
+oid sha256:e3aecfcc8de69f3bff7db5722d296b5de83a553fb540f78686f71ea94f498c14
+size 1832
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_4_stops.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_4_stops.png
index 05a6349..8659de9 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_4_stops.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_4_stops.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1f31616c5347425a4cd661d6d9415ab7ed8b14fa6bcaa2277bb2ca57a2ef43c9
-size 2554
+oid sha256:66a7b88735ef6b4508c6ddbaee9c22eedb25f2753355b0c2d3819c443153f38f
+size 2549
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_c0_bigger.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_c0_bigger.png
index 9014b57..a5c9aca 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_c0_bigger.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_c0_bigger.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:32166e19224571132f985b41f60efc6ddcafaefa3ff65d1883c4b1ffbfb9e4e4
-size 2823
+oid sha256:afbc7185a6d5fbd1fc6658d9188cee985d82958a52b695524bf407505c35e944
+size 2820
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_bottom_left.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_bottom_left.png
index d8528a3..523ce53 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_bottom_left.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_bottom_left.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:dd41f7cee4f0ea9a1a76ecc0fb62a2dc49d4b9b2ef4d15a2a6185a9b929071a4
-size 5531
+oid sha256:a0098c400931d6e1f357a97113a4a92e08814f6c052792aad6ffc5fe9e6612cc
+size 5533
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_bottom_right.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_bottom_right.png
index a4798e8..62009c1 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_bottom_right.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_bottom_right.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:82163de8e7d6136eeab0270a1303255e54d10b31ceab023252695037d46d69d7
-size 5519
+oid sha256:3b3652268eb4be0f1d36882d5d07a31b8ee674718ad65d876de16fbc78841f73
+size 5526
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_top_left.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_top_left.png
index 6dc449c..a9c6f03 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_top_left.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_top_left.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:64f2a7842e59fed9480bf30440c9a72d7efb8c2f16bee4b5eaba9956f4d25d50
-size 5421
+oid sha256:5af05767fbfc6462a2533d9a79560ef4c9154e017342b6aa49b724ffeb109878
+size 5399
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_top_right.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_top_right.png
index 3f65a99..8fb8b0d 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_top_right.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_center_offset_top_right.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4acf3782a25167cc410a9a1a98b04b7219719734971dd18ac6f5b609191a60fa
-size 5535
+oid sha256:f5d51721b3b42c4f2421a5ab2e7a8cbd71dcaa95e8d27c4e50343a4529b22c68
+size 5537
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_focal_on_circle.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_focal_on_circle.png
index 8a2499c..cc6798f 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_focal_on_circle.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_focal_on_circle.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8eb22784a41237c7a0737eb4e21e0dabfaf4b83c0cab2094055e3560583ed444
-size 1350
+oid sha256:2414285d5e6d60c20dd6e8c5f430fbe23a7d8ca9bdd6a5ba90647fc3e4b9e9c8
+size 1351
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_natively_focal.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_natively_focal.png
index c4981ec..e3b8ae6 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_natively_focal.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_natively_focal.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7fadac6e7f4903a9ed6f1d2c385892c977c9543d4461937977754136f745df53
-size 2816
+oid sha256:cf9cab3557e195ccd9d86b3383324cd223f80a09157b61819282605e71c5a3d7
+size 2794
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_c0_larger.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_c0_larger.png
index 60fab80..64b527a 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_c0_larger.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_c0_larger.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8c4cc8df97176eef56732c74fdc29ab9dc1d2fb4e39c30ee9a730920c6a2b0c1
-size 1168
+oid sha256:c3a0a48e59be9a451e4cd43a155a4153fde49146b3b586c2617cbda02d763139
+size 1162
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_c0_smaller.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_c0_smaller.png
index 926b27e..59c077f 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_c0_smaller.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_c0_smaller.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a0e9d6b660d1ab98a44868ccaf84f7f9191e7a2df416eb233b3769f097332a56
-size 899
+oid sha256:a5282cac5c6a3c8a50183c016f578614ce91993dff15d5a796653890eb41e3c0
+size 898
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_same_size.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_same_size.png
index 28a6a6d..00dee1e 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_same_size.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_non_overlapping_same_size.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5711ce3cfde627997e4d7b227825f589285bc6d5cec3bfd3c1da5f17bb79593d
-size 807
+oid sha256:9af665d5e231fdfbcaf8e3155a4c1a73ff3e257034993c556ca5de74a4fb3735
+size 802
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_spread_method_reflect.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_spread_method_reflect.png
index e2fd4c0..6903607 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_spread_method_reflect.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_spread_method_reflect.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6efa4201099a23f7f168a8f0256f43ea56d127616ba21a44d8eeb80a05bc47dd
-size 5408
+oid sha256:9e5fa46dc09ba9aba16d679dd267a894cd0382fd34fd2cb56803ee4e2c6ddb71
+size 5407
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_swapped.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_swapped.png
index 35080bc..b3ecd1e 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_swapped.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_swapped.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2b7e95352c4aa19c0c2144ad555d5500b33cb5bbb6b560bcb0d9e1e877107f8a
-size 2657
+oid sha256:4b4a4aa76a87e5772dae70ba0398db6194cbc41a2c814c216cbb06c4629fd90e
+size 2643
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_negative_scale.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_negative_scale.png
index 398b66b..e830b7b 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_negative_scale.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_negative_scale.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:18a56ce62c2691ee99fe72a7375dd3a562d68858b8815a210ce0728a53a18013
-size 1770
+oid sha256:fde6c6a15979fe5343904e6e5064d042d2796a8ad9f7b65d16c269884e5f4671
+size 1773
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale.png
index 398b66b..e830b7b 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:18a56ce62c2691ee99fe72a7375dd3a562d68858b8815a210ce0728a53a18013
-size 1770
+oid sha256:fde6c6a15979fe5343904e6e5064d042d2796a8ad9f7b65d16c269884e5f4671
+size 1773
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale_and_translate.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale_and_translate.png
index 398b66b..e830b7b 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale_and_translate.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale_and_translate.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:18a56ce62c2691ee99fe72a7375dd3a562d68858b8815a210ce0728a53a18013
-size 1770
+oid sha256:fde6c6a15979fe5343904e6e5064d042d2796a8ad9f7b65d16c269884e5f4671
+size 1773
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale_non_uniform.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale_non_uniform.png
index 0856fb7..d1f7800 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale_non_uniform.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_radial_with_transform_scale_non_uniform.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:dfc30c0b203dd9dd4d61abb9fce4d79c886f3cc4f7e1fdc3ec0b09a3a657fc55
-size 1732
+oid sha256:daa6dc043412d51b4c6e4675bffb7494b273898a11c4d8642973683e4baa8365
+size 1735
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_2_stops.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_2_stops.png
index 1b2a242..e07d365 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_2_stops.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_2_stops.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7193de7eea6d9cc48a44b96e05c2e90377c71d3b63d8225ce8ed7fcdb83683b4
-size 2434
+oid sha256:491b01d4bd9aa7eb4b5ad48528e3f665f7e19fb69013bca0e356a05cb03c081b
+size 1865
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_2_stops_with_alpha.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_2_stops_with_alpha.png
index bac89ae..987e8c5 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_2_stops_with_alpha.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_2_stops_with_alpha.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c2e3bab1c3ad27de78dde7f38c613e512415a35001e9bf308f84c0d1001b3bbf
-size 2957
+oid sha256:304af60ea89dc393fe69ef78da79f30953131c54a2577bebc343221c123bac3f
+size 2228
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_4_stops.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_4_stops.png
index 733456f..126e8fe 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_4_stops.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_4_stops.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f5a218c27c0f405818d7bc4a40d1a4e6947467c81fb65df3cb406dbbe6e625df
-size 3383
+oid sha256:1d9c5401ccbc483b2a2d0f07ce7f9aabaee96e247f8b36520241a384702b9838
+size 3384
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_complex_shape.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_complex_shape.png
index f7ca15c..9b60af1 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_complex_shape.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_complex_shape.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:5576b2d590c8c082f1c117cac1bdb593299a0ffd842341514157f1fbad625d7e
-size 3255
+oid sha256:bdc1973c732e38b070a4bc23a8743549107e62d7c06b2de765c61deb0da19ce9
+size 3247
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_not_in_center.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_not_in_center.png
index 0cd9e5c..3de042c 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_not_in_center.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_not_in_center.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:cdfac6d5b03c50b65cf331a6c9d2942f489f1dac66563597acde884042e27c76
-size 2254
+oid sha256:f628bb4705b8bbcd73f3d99bc1a60ff455a07ddc7b898ee7c4398edd942daaa0
+size 1788
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_pad.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_pad.png
index 750c14f..02c5669 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_pad.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_pad.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6fffe33810627764ae4c5b6b4855003da206e91f2eb3f0d7433a146c9cf7ff23
-size 1389
+oid sha256:09c4aabf271a75b31b94e81017009807546117de33ee8dac30d286fbb232e938
+size 1382
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_reflect.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_reflect.png
index 7bb79aa..870585d 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_reflect.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_reflect.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ce838296c5c9cd20528f3b8e6090268537aac18c6e4e99b120033545133de062
-size 6447
+oid sha256:eeff76b10377bbf43ea27e14e95b7e4b94d033e90d5de379b5232cdf0e94aa98
+size 6452
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_repeat.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_repeat.png
index c2730c2..cbf7a76 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_repeat.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_spread_method_repeat.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:031821f0593badc957400a1694ec5e5079dce749e7082022a7136f38df250aba
-size 6722
+oid sha256:e8d606edd0d50907d970bada7a3572d7952eeb500f36fe8def3f87868b651cdb
+size 6696
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_with_transform_rotate_2.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_with_transform_rotate_2.png
index 71bd7d7..faf7636 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_with_transform_rotate_2.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_with_transform_rotate_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:377f81e818c0aafa803fa6e1b259e4845dba5249dbd0b44bd5727d0400406710
-size 1885
+oid sha256:8844776e1d7d649ccdc53227263dc28c43f1f591ad354266bc2464b4998e8dff
+size 1880
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_with_transform_scale_non_uniform.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_with_transform_scale_non_uniform.png
index db20d5d..bb8f56a 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_with_transform_scale_non_uniform.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_sweep_with_transform_scale_non_uniform.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c31a4ab2be973e4f0e51f31db6b50a2278afe231ad97bfd6f4a2020f788307eb
-size 1235
+oid sha256:963b43dc98fa660f12dcb359e2d157b4db91ede54d97d9b6139c6fbadcfbb8f3
+size 1234
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_1.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_1.png
index bee0416..d6277a7 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_1.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c2061066c09dfb58516026ddff4c29699480eab00d64b8d2bb71842b8dfb5c21
-size 291
+oid sha256:428ee76355d09e08f6fafd0486a1da2e89a599d1a91454aff7e5f81e1b165feb
+size 287
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_2.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_2.png
index cf7404d..2a9c695 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_2.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_2.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bfaa54bbf4612a18eba567032777cbae0e235b5ea07faac2aba649bd8c6b61a5
-size 655
+oid sha256:5692de8a250f22944eb885dda7a66e6436c52a721973d6f2ea4d4ce3c1553546
+size 652
diff --git a/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_3.png b/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_3.png
index 03d79f9..67e3b8d 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_3.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/gradient_with_color_spaces_3.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:b7cc2150e2580c6e901a684452cc02740fe60dbf53b2ea5a225dda10d989ce9c
-size 780
+oid sha256:6449699600b4361c03127784dcad78a8e8b788bf4a0290d49ffba319163474b3
+size 781
diff --git a/sparse_strips/vello_sparse_tests/snapshots/layer_multiple_properties_1.png b/sparse_strips/vello_sparse_tests/snapshots/layer_multiple_properties_1.png
index ceba61f..3c20fc4 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/layer_multiple_properties_1.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/layer_multiple_properties_1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:82224aa80df9966e22739a8de65844aef955108129f7c9813067eb3111111858
-size 1123
+oid sha256:87c472a0953b56af36dd3105a9e87c9297a7d9216525d6fb57f4d6f82a34cd9b
+size 1139
diff --git a/sparse_strips/vello_sparse_tests/snapshots/mask_alpha.png b/sparse_strips/vello_sparse_tests/snapshots/mask_alpha.png
index bd8a31a..68a13c1 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/mask_alpha.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/mask_alpha.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:60733dea33f69381a988228eb3a1ef79dec2772be5f5cba3233f599efea7b7b3
-size 239
+oid sha256:0c4a5beaee4cd4f2ae76436867bcb3f10ba85762d7cc561085ee25744aa6a06b
+size 243
diff --git a/sparse_strips/vello_sparse_tests/snapshots/mask_luminance.png b/sparse_strips/vello_sparse_tests/snapshots/mask_luminance.png
index e52d5d8..685f5be 100644
--- a/sparse_strips/vello_sparse_tests/snapshots/mask_luminance.png
+++ b/sparse_strips/vello_sparse_tests/snapshots/mask_luminance.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:f9d9692bbce31dc35fcfc98897cb90aa8c1b2c935f832e97c48f97bf3d3fb31a
-size 266
+oid sha256:e8d6253f7fe03ced0cdef1eaa2b58dcd305166697051ce8ddc82b5385374cdd4
+size 261