Pass in `transform` for paint types separately

By passing the paint transform in separately, the transform can be moved
out of the paint types. This allows using existing ecosystem types
directly (like `peniko::Gradient`).
diff --git a/sparse_strips/vello_api/src/paint.rs b/sparse_strips/vello_api/src/paint.rs
index 8ce558b..0185e95 100644
--- a/sparse_strips/vello_api/src/paint.rs
+++ b/sparse_strips/vello_api/src/paint.rs
@@ -3,11 +3,12 @@
 
 //! Types for paints.
 
-use crate::kurbo::{Affine, Point};
 use crate::pixmap::Pixmap;
 use alloc::sync::Arc;
-use peniko::color::{AlphaColor, ColorSpaceTag, HueDirection, PremulRgba8, Srgb};
-use peniko::{ColorStops, GradientKind, ImageQuality};
+use peniko::{
+    Gradient, ImageQuality,
+    color::{AlphaColor, PremulRgba8, Srgb},
+};
 
 /// A paint that needs to be resolved via its index.
 // In the future, we might add additional flags, that's why we have
@@ -54,64 +55,6 @@
     }
 }
 
-// TODO: Replace this with the peniko type, once it supports transforms.
-/// A gradient.
-#[derive(Debug, Clone)]
-pub struct Gradient {
-    /// The underlying kind of gradient.
-    pub kind: GradientKind,
-    /// The stops that makes up the gradient.
-    ///
-    /// Note that the first stop must have an offset of 0.0 and the last stop
-    /// must have an offset of 1.0. In addition to that, the stops must be sorted
-    /// with offsets in ascending order.
-    pub stops: ColorStops,
-    /// A transformation to apply to the gradient.
-    pub transform: Affine,
-    /// The extend of the gradient.
-    pub extend: peniko::Extend,
-    /// The color space to be used for interpolation.
-    ///
-    /// The colors in the color stops will be converted to this color space.
-    ///
-    /// This defaults to [sRGB](ColorSpaceTag::Srgb).
-    pub interpolation_cs: ColorSpaceTag,
-    /// When interpolating within a cylindrical color space, the direction for the hue.
-    ///
-    /// This is interpreted as described in [CSS Color Module Level 4 § 12.4].
-    ///
-    /// [CSS Color Module Level 4 § 12.4]: https://drafts.csswg.org/css-color/#hue-interpolation
-    pub hue_direction: HueDirection,
-}
-
-impl Default for Gradient {
-    fn default() -> Self {
-        Self {
-            kind: GradientKind::Linear {
-                start: Point::default(),
-                end: Point::default(),
-            },
-            transform: Affine::IDENTITY,
-            interpolation_cs: ColorSpaceTag::Srgb,
-            extend: Default::default(),
-            hue_direction: Default::default(),
-            stops: Default::default(),
-        }
-    }
-}
-
-impl Gradient {
-    /// Returns the gradient with the alpha component for all color stops
-    /// multiplied by `alpha`.
-    #[must_use]
-    pub fn multiply_alpha(mut self, alpha: f32) -> Self {
-        self.stops
-            .iter_mut()
-            .for_each(|stop| *stop = stop.multiply_alpha(alpha));
-        self
-    }
-}
-
 /// An image.
 #[derive(Debug, Clone)]
 pub struct Image {
@@ -123,8 +66,6 @@
     pub y_extend: peniko::Extend,
     /// Hint for desired rendering quality.
     pub quality: ImageQuality,
-    /// A transform to apply to the image.
-    pub transform: Affine,
 }
 
 /// A premultiplied color.
diff --git a/sparse_strips/vello_bench/src/cpu_fine.rs b/sparse_strips/vello_bench/src/cpu_fine.rs
index 25caa3f..d3a125d 100644
--- a/sparse_strips/vello_bench/src/cpu_fine.rs
+++ b/sparse_strips/vello_bench/src/cpu_fine.rs
@@ -10,10 +10,12 @@
 use vello_common::color::DynamicColor;
 use vello_common::color::palette::css::{BLUE, GREEN, RED, ROYAL_BLUE, YELLOW};
 use vello_common::encode::{EncodeExt, EncodedPaint};
-use vello_common::kurbo::Point;
-use vello_common::paint::{Gradient, Paint, PremulColor};
+use vello_common::kurbo::{Affine, Point};
+use vello_common::paint::{Paint, PremulColor};
 use vello_common::peniko;
-use vello_common::peniko::{BlendMode, ColorStop, ColorStops, Compose, GradientKind, Mix};
+use vello_common::peniko::{
+    BlendMode, ColorStop, ColorStops, Compose, Gradient, GradientKind, Mix,
+};
 use vello_common::tile::Tile;
 use vello_cpu::fine::{Fine, SCRATCH_BUF_SIZE};
 
@@ -75,7 +77,7 @@
                 ..Default::default()
             };
 
-            let paint = grad.encode_into(&mut paints);
+            let paint = grad.encode_into(&mut paints, Affine::IDENTITY);
 
             fill_single!($name, &paint, &paints, WideTile::WIDTH as usize);
         };
@@ -116,7 +118,7 @@
                 ..Default::default()
             };
 
-            let paint = grad.encode_into(&mut paints);
+            let paint = grad.encode_into(&mut paints, Affine::IDENTITY);
 
             fill_single!($name, &paint, &paints, WideTile::WIDTH as usize);
         };
@@ -161,7 +163,7 @@
                 ..Default::default()
             };
 
-            let paint = grad.encode_into(&mut paints);
+            let paint = grad.encode_into(&mut paints, Affine::IDENTITY);
 
             fill_single!($name, &paint, &paints, WideTile::WIDTH as usize);
         };
diff --git a/sparse_strips/vello_common/src/encode.rs b/sparse_strips/vello_common/src/encode.rs
index 9501ba7..326ede1 100644
--- a/sparse_strips/vello_common/src/encode.rs
+++ b/sparse_strips/vello_common/src/encode.rs
@@ -7,7 +7,7 @@
 use crate::color::{ColorSpaceTag, HueDirection, PremulColor, Srgb, gradient};
 use crate::encode::private::Sealed;
 use crate::kurbo::{Affine, Point, Vec2};
-use crate::peniko::{ColorStop, Extend, GradientKind, ImageQuality};
+use crate::peniko::{ColorStop, Extend, Gradient, GradientKind, ImageQuality};
 use crate::pixmap::Pixmap;
 use alloc::borrow::Cow;
 use alloc::sync::Arc;
@@ -15,7 +15,7 @@
 use core::f32::consts::PI;
 use core::iter;
 use smallvec::SmallVec;
-use vello_api::paint::{Gradient, Image, IndexedPaint, Paint};
+use vello_api::paint::{Image, IndexedPaint, Paint};
 
 const DEGENERATE_THRESHOLD: f32 = 1.0e-6;
 const NUDGE_VAL: f32 = 1.0e-7;
@@ -24,12 +24,12 @@
 pub trait EncodeExt: private::Sealed {
     /// Encode the gradient and push it into a vector of encoded paints, returning
     /// the corresponding paint in the process. This will also validate the gradient.
-    fn encode_into(&self, paints: &mut Vec<EncodedPaint>) -> Paint;
+    fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint;
 }
 
 impl EncodeExt for Gradient {
     /// Encode the gradient into a paint.
-    fn encode_into(&self, paints: &mut Vec<EncodedPaint>) -> Paint {
+    fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
         // First make sure that the gradient is valid and not degenerate.
         if let Err(paint) = validate(self) {
             return paint;
@@ -205,8 +205,8 @@
         // adding 0.5.
         // Finally, we need to apply the _inverse_ transform to the point so that we can account
         // for the transform on the gradient.
-        let transform = Affine::translate((x_offset as f64 + 0.5, y_offset as f64 + 0.5))
-            * self.transform.inverse();
+        let transform =
+            Affine::translate((x_offset as f64 + 0.5, y_offset as f64 + 0.5)) * transform.inverse();
 
         // One possible approach of calculating the positions would be to apply the above
         // transform to _each_ pixel that we render in the wide tile. However, a much better
@@ -460,10 +460,10 @@
 impl Sealed for Image {}
 
 impl EncodeExt for Image {
-    fn encode_into(&self, paints: &mut Vec<EncodedPaint>) -> Paint {
+    fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
         let idx = paints.len();
 
-        let transform = self.transform.inverse();
+        let transform = transform.inverse();
         // TODO: This is somewhat expensive for large images, maybe it's not worth optimizing
         // non-opaque images in the first place..
         let has_opacities = self.pixmap.data().chunks(4).any(|c| c[3] != 255);
@@ -702,12 +702,10 @@
 }
 
 mod private {
-    use vello_api::paint::Gradient;
-
     #[allow(unnameable_types, reason = "We make it unnameable on purpose")]
     pub trait Sealed {}
 
-    impl Sealed for Gradient {}
+    impl Sealed for super::Gradient {}
 }
 
 #[cfg(test)]
@@ -719,6 +717,7 @@
     use crate::peniko::{ColorStop, ColorStops, GradientKind};
     use alloc::vec;
     use smallvec::smallvec;
+    use vello_api::kurbo::Affine;
 
     #[test]
     fn gradient_missing_stops() {
@@ -732,7 +731,10 @@
             ..Default::default()
         };
 
-        assert_eq!(gradient.encode_into(&mut buf), BLACK.into());
+        assert_eq!(
+            gradient.encode_into(&mut buf, Affine::IDENTITY),
+            BLACK.into()
+        );
     }
 
     #[test]
@@ -752,7 +754,10 @@
         };
 
         // Should return the color of the first stop.
-        assert_eq!(gradient.encode_into(&mut buf), GREEN.into());
+        assert_eq!(
+            gradient.encode_into(&mut buf, Affine::IDENTITY),
+            GREEN.into()
+        );
     }
 
     #[test]
@@ -777,7 +782,10 @@
             ..Default::default()
         };
 
-        assert_eq!(gradient.encode_into(&mut buf), GREEN.into());
+        assert_eq!(
+            gradient.encode_into(&mut buf, Affine::IDENTITY),
+            GREEN.into()
+        );
     }
 
     #[test]
@@ -802,7 +810,10 @@
             ..Default::default()
         };
 
-        assert_eq!(gradient.encode_into(&mut buf), GREEN.into());
+        assert_eq!(
+            gradient.encode_into(&mut buf, Affine::IDENTITY),
+            GREEN.into()
+        );
     }
 
     #[test]
@@ -827,7 +838,10 @@
             ..Default::default()
         };
 
-        assert_eq!(gradient.encode_into(&mut buf), GREEN.into());
+        assert_eq!(
+            gradient.encode_into(&mut buf, Affine::IDENTITY),
+            GREEN.into()
+        );
     }
 
     #[test]
@@ -853,7 +867,10 @@
             ..Default::default()
         };
 
-        assert_eq!(gradient.encode_into(&mut buf), GREEN.into());
+        assert_eq!(
+            gradient.encode_into(&mut buf, Affine::IDENTITY),
+            GREEN.into()
+        );
     }
 
     #[test]
@@ -880,6 +897,9 @@
             ..Default::default()
         };
 
-        assert_eq!(gradient.encode_into(&mut buf), GREEN.into());
+        assert_eq!(
+            gradient.encode_into(&mut buf, Affine::IDENTITY),
+            GREEN.into()
+        );
     }
 }
diff --git a/sparse_strips/vello_cpu/src/render.rs b/sparse_strips/vello_cpu/src/render.rs
index 26c70d7..3bab858 100644
--- a/sparse_strips/vello_cpu/src/render.rs
+++ b/sparse_strips/vello_cpu/src/render.rs
@@ -33,6 +33,7 @@
     pub(crate) tiles: Tiles,
     pub(crate) strip_buf: Vec<Strip>,
     pub(crate) paint: PaintType,
+    pub(crate) paint_transform: Affine,
     pub(crate) stroke: Stroke,
     pub(crate) transform: Affine,
     pub(crate) fill_rule: Fill,
@@ -52,6 +53,7 @@
         let transform = Affine::IDENTITY;
         let fill_rule = Fill::NonZero;
         let paint = BLACK.into();
+        let paint_transform = Affine::IDENTITY;
         let stroke = Stroke {
             width: 1.0,
             join: Join::Bevel,
@@ -71,6 +73,7 @@
             strip_buf,
             transform,
             paint,
+            paint_transform,
             fill_rule,
             stroke,
             encoded_paints,
@@ -80,15 +83,17 @@
     fn encode_current_paint(&mut self) -> Paint {
         match self.paint.clone() {
             PaintType::Solid(s) => s.into(),
-            PaintType::Gradient(mut g) => {
+            PaintType::Gradient(g) => {
                 // TODO: Add caching?
-                g.transform = self.transform * g.transform;
-                g.encode_into(&mut self.encoded_paints)
+                g.encode_into(
+                    &mut self.encoded_paints,
+                    self.transform * self.paint_transform,
+                )
             }
-            PaintType::Image(mut i) => {
-                i.transform = self.transform * i.transform;
-                i.encode_into(&mut self.encoded_paints)
-            }
+            PaintType::Image(i) => i.encode_into(
+                &mut self.encoded_paints,
+                self.transform * self.paint_transform,
+            ),
         }
     }
 
@@ -196,6 +201,16 @@
         self.paint = paint.into();
     }
 
+    /// Set the current paint transform.
+    pub fn set_paint_transform(&mut self, paint_transform: Affine) {
+        self.paint_transform = paint_transform;
+    }
+
+    /// Reset the current paint transform.
+    pub fn reset_paint_transform(&mut self) {
+        self.paint_transform = Affine::IDENTITY;
+    }
+
     /// Set the current fill rule.
     pub fn set_fill_rule(&mut self, fill_rule: Fill) {
         self.fill_rule = fill_rule;
diff --git a/sparse_strips/vello_cpu/tests/gradient.rs b/sparse_strips/vello_cpu/tests/gradient.rs
index da6e97e..38595ed 100644
--- a/sparse_strips/vello_cpu/tests/gradient.rs
+++ b/sparse_strips/vello_cpu/tests/gradient.rs
@@ -6,8 +6,7 @@
 use vello_common::color::palette::css::{BLACK, BLUE, WHITE, YELLOW};
 use vello_common::color::{ColorSpaceTag, DynamicColor};
 use vello_common::kurbo::{Point, Rect};
-use vello_common::paint::Gradient;
-use vello_common::peniko::{ColorStop, ColorStops, GradientKind};
+use vello_common::peniko::{ColorStop, ColorStops, Gradient, GradientKind};
 
 pub(crate) const fn tan_45() -> f64 {
     1.0
@@ -134,8 +133,7 @@
     };
     use std::f64::consts::PI;
     use vello_common::kurbo::{Affine, Point, Rect};
-    use vello_common::paint::Gradient;
-    use vello_common::peniko::GradientKind;
+    use vello_common::peniko::{Gradient, GradientKind};
 
     #[test]
     fn gradient_linear_2_stops() {
@@ -541,8 +539,7 @@
     };
     use std::f64::consts::PI;
     use vello_common::kurbo::{Affine, Point, Rect};
-    use vello_common::paint::Gradient;
-    use vello_common::peniko::GradientKind::Radial;
+    use vello_common::peniko::{Gradient, GradientKind::Radial};
 
     macro_rules! simple {
         ($stops:expr, $name:expr) => {
@@ -958,8 +955,7 @@
     };
     use std::f64::consts::PI;
     use vello_common::kurbo::{Affine, Point, Rect};
-    use vello_common::paint::Gradient;
-    use vello_common::peniko::GradientKind;
+    use vello_common::peniko::{Gradient, GradientKind};
 
     macro_rules! basic {
         ($stops:expr, $name:expr, $center:expr) => {
diff --git a/sparse_strips/vello_cpu/tests/image.rs b/sparse_strips/vello_cpu/tests/image.rs
index b2f0c2f..1959e46 100644
--- a/sparse_strips/vello_cpu/tests/image.rs
+++ b/sparse_strips/vello_cpu/tests/image.rs
@@ -51,8 +51,8 @@
             x_extend: $x_repeat,
             y_extend: $y_repeat,
             quality: ImageQuality::Low,
-            transform: Affine::translate((45.0, 45.0)),
         });
+        ctx.set_paint_transform(Affine::translate((45.0, 45.0)));
         ctx.fill_rect(&rect);
 
         check_ref(&ctx, $name);
@@ -98,7 +98,6 @@
             x_extend: Extend::Repeat,
             y_extend: Extend::Repeat,
             quality: ImageQuality::Low,
-            transform: Affine::IDENTITY,
         };
 
         ctx.set_transform($transform);
@@ -270,7 +269,6 @@
         x_extend: Extend::Repeat,
         y_extend: Extend::Repeat,
         quality: ImageQuality::Low,
-        transform: Affine::IDENTITY,
     };
 
     ctx.set_paint(image);
@@ -292,7 +290,6 @@
         x_extend: Extend::Repeat,
         y_extend: Extend::Repeat,
         quality: ImageQuality::Low,
-        transform: Affine::IDENTITY,
     };
 
     ctx.set_paint(image);
@@ -311,7 +308,6 @@
             x_extend: Extend::Repeat,
             y_extend: Extend::Repeat,
             quality: ImageQuality::Low,
-            transform: Affine::IDENTITY,
         };
 
         ctx.set_paint(image);
@@ -351,10 +347,10 @@
             x_extend: $extend,
             y_extend: $extend,
             quality: $quality,
-            transform: $transform,
         };
 
         ctx.set_paint(image);
+        ctx.set_paint_transform($transform);
         ctx.fill_rect(&rect);
 
         check_ref(&ctx, $name);
diff --git a/sparse_strips/vello_cpu/tests/mask.rs b/sparse_strips/vello_cpu/tests/mask.rs
index 4503c70..5b76b23 100644
--- a/sparse_strips/vello_cpu/tests/mask.rs
+++ b/sparse_strips/vello_cpu/tests/mask.rs
@@ -7,8 +7,7 @@
 use vello_common::color::palette::css::{BLACK, LIME, RED, YELLOW};
 use vello_common::kurbo::{Point, Rect};
 use vello_common::mask::Mask;
-use vello_common::paint::Gradient;
-use vello_common::peniko::{ColorStop, ColorStops, GradientKind};
+use vello_common::peniko::{ColorStop, ColorStops, Gradient, GradientKind};
 use vello_cpu::{Pixmap, RenderContext};
 
 pub(crate) fn example_mask(alpha_mask: bool) -> Mask {
diff --git a/sparse_strips/vello_cpu/tests/mix.rs b/sparse_strips/vello_cpu/tests/mix.rs
index f34e13c..5f8f3ba 100644
--- a/sparse_strips/vello_cpu/tests/mix.rs
+++ b/sparse_strips/vello_cpu/tests/mix.rs
@@ -7,8 +7,8 @@
 use vello_common::color::palette::css::{BLUE, LIME, MAGENTA, RED, YELLOW};
 use vello_common::color::{AlphaColor, DynamicColor, Srgb};
 use vello_common::kurbo::{Affine, Point, Rect};
-use vello_common::paint::{Gradient, Image};
-use vello_common::peniko::{BlendMode, Compose, Extend, Mix};
+use vello_common::paint::Image;
+use vello_common::peniko::{BlendMode, Compose, Extend, Gradient, Mix};
 use vello_common::peniko::{ColorStop, ColorStops, GradientKind, ImageQuality};
 
 // The outputs have been compared visually with tiny-skia, and except for two cases (where tiny-skia
@@ -52,7 +52,6 @@
         x_extend: Extend::Pad,
         y_extend: Extend::Pad,
         quality: ImageQuality::Low,
-        transform: Affine::IDENTITY,
     };
 
     ctx.set_transform(Affine::translate((10.0, 10.0)));