Merge branch 'main' into precompute

# Conflicts:
#	sparse_strips/vello_bench/src/cpu_fine.rs
diff --git a/sparse_strips/vello_api/src/paint.rs b/sparse_strips/vello_api/src/paint.rs
index 13fb370..595d8f8 100644
--- a/sparse_strips/vello_api/src/paint.rs
+++ b/sparse_strips/vello_api/src/paint.rs
@@ -36,10 +36,10 @@
 /// 2) Indexed paints, which can represent any arbitrary, more complex paint that is
 ///    determined by the frontend. The intended way of using this is to store a vector
 ///    of paints and store its index inside `IndexedPaint`.
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq)]
 pub enum Paint {
     /// A premultiplied RGBA8 color.
-    Solid(PremulRgba8),
+    Solid(PremulColor),
     /// A paint that needs to be resolved via an index.
     Indexed(IndexedPaint),
 }
@@ -50,7 +50,7 @@
         // Since we only do that conversion once per path it might not be critical, but should
         // still be measured. This also applies to all other usages of `to_rgba8` in the current
         // code.
-        Self::Solid(value.premultiply().to_rgba8())
+        Self::Solid(PremulColor::new(value))
     }
 }
 
@@ -127,6 +127,40 @@
     pub transform: Affine,
 }
 
+/// A premultiplied color.
+#[derive(Debug, Clone, PartialEq, Copy)]
+pub struct PremulColor {
+    premul_u8: PremulRgba8,
+    premul_f32: peniko::color::PremulColor<Srgb>,
+}
+
+impl PremulColor {
+    /// Create a new premultiplied color.
+    pub fn new(color: AlphaColor<Srgb>) -> Self {
+        let premul = color.premultiply();
+
+        Self {
+            premul_u8: premul.to_rgba8(),
+            premul_f32: premul,
+        }
+    }
+
+    /// Return the color as a premultiplied RGBA8 color.
+    pub fn as_premul_rgba8(&self) -> PremulRgba8 {
+        self.premul_u8
+    }
+
+    /// Return the color as a premultiplied RGBAF32 color.
+    pub fn as_premul_f32(&self) -> peniko::color::PremulColor<Srgb> {
+        self.premul_f32
+    }
+
+    /// Return whether the color is opaque (i.e. has no transparency).
+    pub fn is_opaque(&self) -> bool {
+        self.premul_f32.components[3] == 1.0
+    }
+}
+
 /// A kind of paint that can be used for filling and stroking shapes.
 #[derive(Debug, Clone)]
 pub enum PaintType {
diff --git a/sparse_strips/vello_bench/src/cpu_fine.rs b/sparse_strips/vello_bench/src/cpu_fine.rs
index 62e6bb3..013a420 100644
--- a/sparse_strips/vello_bench/src/cpu_fine.rs
+++ b/sparse_strips/vello_bench/src/cpu_fine.rs
@@ -9,9 +9,9 @@
 use vello_common::coarse::WideTile;
 use vello_common::color::DynamicColor;
 use vello_common::color::palette::css::{BLUE, GREEN, RED, ROYAL_BLUE, YELLOW};
-use vello_common::encode::EncodeExt;
-use vello_common::kurbo::Point;
-use vello_common::paint::{Gradient, Paint};
+use vello_common::encode::{EncodeExt, EncodedPaint};
+use vello_common::kurbo::{Affine, Point};
+use vello_common::paint::{Gradient, Paint, PremulColor};
 use vello_common::peniko;
 use vello_common::peniko::{ColorStop, ColorStops, GradientKind};
 use vello_common::tile::Tile;
@@ -21,12 +21,15 @@
     let mut g = c.benchmark_group("fine/fill");
 
     macro_rules! fill_single {
-        ($name:ident, $paint:expr, $paints:expr) => {
+        ($name:ident, $paint:expr, $paints:expr, $width:expr) => {
             g.bench_function(stringify!($name), |b| {
                 let mut fine = Fine::new(WideTile::WIDTH, Tile::HEIGHT);
 
+                let paint = $paint;
+                let paints: &[EncodedPaint] = $paints;
+
                 b.iter(|| {
-                    fine.fill(0, WideTile::WIDTH as usize, $paint, $paints);
+                    fine.fill(0, $width, paint, paints);
 
                     std::hint::black_box(&fine);
                 })
@@ -36,13 +39,21 @@
 
     fill_single!(
         solid_opaque,
-        &Paint::Solid(ROYAL_BLUE.premultiply().to_rgba8()),
-        &[]
+        &Paint::Solid(PremulColor::new(ROYAL_BLUE)),
+        &[],
+        WideTile::WIDTH as usize
     );
     fill_single!(
-        sold_transparent,
-        &Paint::Solid(ROYAL_BLUE.with_alpha(0.2).premultiply().to_rgba8()),
-        &[]
+        solid_opaque_short,
+        &Paint::Solid(PremulColor::new(ROYAL_BLUE)),
+        &[],
+        16
+    );
+    fill_single!(
+        solid_transparent,
+        &Paint::Solid(PremulColor::new(ROYAL_BLUE.with_alpha(0.2))),
+        &[],
+        WideTile::WIDTH as usize
     );
 
     macro_rules! fill_single_linear {
@@ -60,7 +71,7 @@
 
             let paint = grad.encode_into(&mut paints);
 
-            fill_single!($name, &paint, &paints);
+            fill_single!($name, &paint, &paints, WideTile::WIDTH as usize);
         };
     }
 
@@ -101,7 +112,7 @@
 
             let paint = grad.encode_into(&mut paints);
 
-            fill_single!($name, &paint, &paints);
+            fill_single!($name, &paint, &paints, WideTile::WIDTH as usize);
         };
     }
 
@@ -146,7 +157,7 @@
 
             let paint = grad.encode_into(&mut paints);
 
-            fill_single!($name, &paint, &paints);
+            fill_single!($name, &paint, &paints, WideTile::WIDTH as usize);
         };
     }
 
@@ -183,12 +194,15 @@
     }
 
     macro_rules! strip_single {
-        ($name:ident, $paint:expr, $paints:expr) => {
+        ($name:ident, $paint:expr, $paints:expr, $width:expr) => {
             g.bench_function(stringify!($name), |b| {
                 let mut fine = Fine::new(WideTile::WIDTH, Tile::HEIGHT);
 
+                let paint = $paint;
+                let paints: &[EncodedPaint] = $paints;
+
                 b.iter(|| {
-                    fine.strip(0, WideTile::WIDTH as usize, &alphas, $paint, $paints);
+                    fine.strip(0, $width, &alphas, paint, paints);
 
                     std::hint::black_box(&fine);
                 })
@@ -198,8 +212,16 @@
 
     strip_single!(
         basic,
-        &Paint::Solid(ROYAL_BLUE.premultiply().to_rgba8()),
-        &[]
+        &Paint::Solid(PremulColor::new(ROYAL_BLUE)),
+        &[],
+        WideTile::WIDTH as usize
+    );
+
+    strip_single!(
+        basic_short,
+        &Paint::Solid(PremulColor::new(ROYAL_BLUE)),
+        &[],
+        8
     );
 
     // There is not really a need to measure performance of complex paint types
diff --git a/sparse_strips/vello_common/src/coarse.rs b/sparse_strips/vello_common/src/coarse.rs
index 3245aff..53fc368 100644
--- a/sparse_strips/vello_common/src/coarse.rs
+++ b/sparse_strips/vello_common/src/coarse.rs
@@ -3,14 +3,11 @@
 
 //! Generating and processing wide tiles.
 
-use crate::{
-    color::{AlphaColor, Srgb},
-    strip::Strip,
-    tile::Tile,
-};
+use crate::color::palette::css::TRANSPARENT;
+use crate::{strip::Strip, tile::Tile};
 use alloc::vec;
 use alloc::{boxed::Box, vec::Vec};
-use vello_api::color::PremulRgba8;
+use vello_api::paint::PremulColor;
 use vello_api::{paint::Paint, peniko::Fill};
 
 /// A container for wide tiles.
@@ -130,7 +127,7 @@
     /// Reset all tiles in the container.
     pub fn reset(&mut self) {
         for tile in &mut self.tiles {
-            tile.bg = AlphaColor::<Srgb>::TRANSPARENT.premultiply().to_rgba8();
+            tile.bg = PremulColor::new(TRANSPARENT);
             tile.cmds.clear();
         }
     }
@@ -616,7 +613,7 @@
     /// The y coordinate of the wide tile.
     pub y: u16,
     /// The background of the tile.
-    pub bg: PremulRgba8,
+    pub bg: PremulColor,
     /// The draw commands of the tile.
     pub cmds: Vec<Cmd>,
 
@@ -635,7 +632,7 @@
         Self {
             x,
             y,
-            bg: AlphaColor::<Srgb>::TRANSPARENT.premultiply().to_rgba8(),
+            bg: PremulColor::new(TRANSPARENT),
             cmds: vec![],
 
             n_zero_clip: 0,
@@ -656,7 +653,8 @@
                 //
                 // However, the extra cost of tracking such optimizations may outweigh the
                 // benefit, especially in hybrid mode with GPU painting.
-                let can_override = x == 0 && width == Self::WIDTH && s.a == 255 && self.n_clip == 0;
+                let can_override =
+                    x == 0 && width == Self::WIDTH && s.is_opaque() && self.n_clip == 0;
                 can_override.then_some(*s)
             } else {
                 // TODO: Implement for indexed paints.
diff --git a/sparse_strips/vello_cpu/src/fine/mod.rs b/sparse_strips/vello_cpu/src/fine/mod.rs
index e0717cb..7569f19 100644
--- a/sparse_strips/vello_cpu/src/fine/mod.rs
+++ b/sparse_strips/vello_cpu/src/fine/mod.rs
@@ -145,18 +145,18 @@
 
         match fill {
             Paint::Solid(color) => {
-                let color = &color.to_u8_array();
+                let color = color.as_premul_rgba8().to_u8_array();
 
                 // If color is completely opaque we can just memcopy the colors.
                 if color[3] == 255 {
                     for t in blend_buf.chunks_exact_mut(COLOR_COMPONENTS) {
-                        t.copy_from_slice(color);
+                        t.copy_from_slice(&color);
                     }
 
                     return;
                 }
 
-                fill::src_over(blend_buf, iter::repeat(*color));
+                fill::src_over(blend_buf, iter::repeat(color));
             }
             Paint::Indexed(paint) => {
                 let encoded_paint = &encoded_paints[paint.index()];
@@ -223,7 +223,11 @@
 
         match fill {
             Paint::Solid(color) => {
-                strip::src_over(blend_buf, iter::repeat(color.to_u8_array()), alphas);
+                strip::src_over(
+                    blend_buf,
+                    iter::repeat(color.as_premul_rgba8().to_u8_array()),
+                    alphas,
+                );
             }
             Paint::Indexed(paint) => {
                 let encoded_paint = &paints[paint.index()];
diff --git a/sparse_strips/vello_cpu/src/render.rs b/sparse_strips/vello_cpu/src/render.rs
index 8a4b9a1..2dd7b00 100644
--- a/sparse_strips/vello_cpu/src/render.rs
+++ b/sparse_strips/vello_cpu/src/render.rs
@@ -198,7 +198,7 @@
                 let wtile = self.wide.get(x, y);
                 fine.set_coords(x, y);
 
-                fine.clear(wtile.bg.to_u8_array());
+                fine.clear(wtile.bg.as_premul_rgba8().to_u8_array());
                 for cmd in &wtile.cmds {
                     fine.run_cmd(cmd, &self.alphas, &self.encoded_paints);
                 }
diff --git a/sparse_strips/vello_hybrid/src/scene.rs b/sparse_strips/vello_hybrid/src/scene.rs
index 952abf4..c7e382b 100644
--- a/sparse_strips/vello_hybrid/src/scene.rs
+++ b/sparse_strips/vello_hybrid/src/scene.rs
@@ -7,7 +7,6 @@
 use alloc::vec;
 use alloc::vec::Vec;
 use vello_common::coarse::{Wide, WideTile};
-use vello_common::color::PremulRgba8;
 use vello_common::flatten::Line;
 use vello_common::glyph::{GlyphRenderer, GlyphRunBuilder, PreparedGlyph};
 use vello_common::kurbo::{Affine, BezPath, Cap, Join, Rect, Shape, Stroke};
@@ -212,7 +211,7 @@
                 let wide_tile = &self.wide.tiles[wide_tile_idx];
                 let wide_tile_x = wide_tile_col * WideTile::WIDTH;
                 let wide_tile_y = wide_tile_row * Tile::HEIGHT;
-                let bg = wide_tile.bg.to_u32();
+                let bg = wide_tile.bg.as_premul_rgba8().to_u32();
                 if bg != 0 {
                     strips.push(GpuStrip {
                         x: wide_tile_x,
@@ -226,8 +225,8 @@
                 for cmd in &wide_tile.cmds {
                     match cmd {
                         vello_common::coarse::Cmd::Fill(fill) => {
-                            let color: PremulRgba8 = match fill.paint {
-                                Paint::Solid(color) => color,
+                            let rgba = match &fill.paint {
+                                Paint::Solid(color) => color.as_premul_rgba8().to_u32(),
                                 Paint::Indexed(_) => unimplemented!(),
                             };
                             strips.push(GpuStrip {
@@ -236,12 +235,12 @@
                                 width: fill.width,
                                 dense_width: 0,
                                 col: 0,
-                                rgba: color.to_u32(),
+                                rgba,
                             });
                         }
                         vello_common::coarse::Cmd::AlphaFill(cmd_strip) => {
-                            let color: PremulRgba8 = match cmd_strip.paint {
-                                Paint::Solid(color) => color,
+                            let rgba = match &cmd_strip.paint {
+                                Paint::Solid(color) => color.as_premul_rgba8().to_u32(),
                                 Paint::Indexed(_) => unimplemented!(),
                             };
 
@@ -255,7 +254,7 @@
                                 col: (cmd_strip.alpha_idx / usize::from(Tile::HEIGHT))
                                     .try_into()
                                     .expect(msg),
-                                rgba: color.to_u32(),
+                                rgba,
                             });
                         }
                         _ => {