Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
258 changes: 147 additions & 111 deletions Cargo.lock

Large diffs are not rendered by default.

31 changes: 20 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ members = [
"sparse_strips/vello_hybrid/examples/webgl",
"sparse_strips/vello_hybrid/examples/winit",
"sparse_strips/vello_toy",
"sparse_strips/vello_dev_macros",
"sparse_strips/vello_sparse_tests",
"image_filters/vello_filters_cpu",
]

[workspace.package]
Expand Down Expand Up @@ -91,42 +94,48 @@ clippy.wildcard_dependencies = "warn"
vello = { version = "0.4.0", path = "vello" }
vello_encoding = { version = "0.4.0", path = "vello_encoding" }
vello_shaders = { version = "0.4.0", path = "vello_shaders" }
bytemuck = { version = "1.22.0", features = ["derive"] }
bytemuck = { version = "1.23.0", features = ["derive"] }
skrifa = "0.26.4"
# The version of kurbo used below should be kept in sync
# with the version of kurbo used by peniko.
peniko = { version = "0.3.1", default-features = false }
peniko = { version = "0.4.0", default-features = false }
# FIXME: This can be removed once peniko supports the schemars feature.
kurbo = { version = "0.11.1", default-features = false }
kurbo = { version = "0.11.2", default-features = false }
futures-intrusive = "0.5.0"
smallvec = "1.15.0"
static_assertions = "1.1.0"
thiserror = "2.0.12"
oxipng = { version = "9.1.4", default-features = false }
oxipng = { version = "9.1.5", default-features = false }
png = "0.17.16"

# The below crates are experimental!
vello_api = { path = "sparse_strips/vello_api", default-features = false }
vello_common = { path = "sparse_strips/vello_common" }
vello_cpu = { path = "sparse_strips/vello_cpu" }
vello_hybrid = { path = "sparse_strips/vello_hybrid" }
vello_hybrid_scenes = { path = "sparse_strips/vello_hybrid/examples/scenes" }
vello_dev_macros = { path = "sparse_strips/vello_dev_macros" }

# NOTE: Make sure to keep this in sync with the version badge in README.md and vello/README.md
wgpu = { version = "24.0.1" }
log = "0.4.27"
image = { version = "0.25.6", default-features = false }

# Used for examples
clap = "4.5.35"
anyhow = "1.0.97"
png = "0.17.16"
clap = "4.5.37"
anyhow = "1.0.98"
pollster = "0.4.0"
web-time = "1.1.0"
wgpu-profiler = "0.21.0"
winit = "0.30.9"
wgpu-profiler = "0.22.0"
winit = "0.30.10"
scenes = { path = "examples/scenes" }
svg = "0.18.0"
criterion = { version = "0.5.1", default-features = false }
rand = { version = "0.9.0", default-features = false, features = ["std_rng"] }
usvg = { version = "0.45.0" }
rand = { version = "0.9.1", default-features = false, features = ["std_rng"] }
usvg = { version = "0.45.1" }
once_cell = "1.21.3"

# Used for testing
proc-macro2 = "1.0.95"
syn = { version = "2.0.101", features = ["full", "extra-traits"] }
quote = "1.0.38"
4 changes: 3 additions & 1 deletion sparse_strips/vello_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ publish = false

[dependencies]
peniko = { workspace = true }
png = { workspace = true, optional = true }

[features]
default = ["std"]
default = ["std", "png"]
std = ["peniko/std"]
libm = ["peniko/libm"]
png = ["std", "dep:png"]
simd = []

[lints]
Expand Down
3 changes: 3 additions & 0 deletions sparse_strips/vello_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@

#![forbid(unsafe_code)]
#![no_std]
extern crate alloc;

pub use peniko;
pub use peniko::color;
pub use peniko::kurbo;
pub mod execute;
pub mod glyph;
pub mod mask;
pub mod paint;
pub mod pixmap;
83 changes: 83 additions & 0 deletions sparse_strips/vello_api/src/mask.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Alpha and luminance masks.

use crate::pixmap::Pixmap;
use alloc::sync::Arc;
use alloc::vec::Vec;

/// A mask.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Mask {
data: Arc<Vec<u8>>,
width: u16,
height: u16,
}
Comment thread
taj-p marked this conversation as resolved.

impl Mask {
/// Create a new alpha mask from the pixmap.
pub fn new_alpha(pixmap: &Pixmap) -> Self {
Self::new_with(pixmap, true)
}

/// Create a new luminance mask from the pixmap.
pub fn new_luminance(pixmap: &Pixmap) -> Self {
Self::new_with(pixmap, false)
}

fn new_with(pixmap: &Pixmap, alpha_mask: bool) -> Self {
let mut data = Vec::with_capacity(pixmap.width() as usize * pixmap.height() as usize);

for pixel in pixmap.data().chunks_exact(4) {
Comment thread
taj-p marked this conversation as resolved.
if alpha_mask {
data.push(pixel[3]);
} else {
let mut r = pixel[0] as f32 / 255.0;
let mut g = pixel[1] as f32 / 255.0;
let mut b = pixel[2] as f32 / 255.0;
let a = pixel[3] as f32 / 255.0;

if pixel[3] != 0 {
r /= a;
g /= a;
b /= a;
}

// See https://www.w3.org/TR/filter-effects-1/#elementdef-fecolormatrix
let luma = r * 0.2126 + g * 0.7152 + b * 0.0722;
#[allow(clippy::cast_possible_truncation, reason = "This cannot overflow")]
data.push(((luma * a) * 255.0 + 0.5) as u8);
Comment thread
taj-p marked this conversation as resolved.
}
}

Self {
data: Arc::new(data),
width: pixmap.width,
height: pixmap.height,
}
}

/// Return the width of the mask.
pub fn width(&self) -> u16 {
self.width
}

/// Return the height of the mask.
pub fn height(&self) -> u16 {
self.height
}

/// Sample the value at a specific location.
///
/// This function might panic or yield a wrong result if the location
/// is out-of-bounds.
Comment thread
taj-p marked this conversation as resolved.
pub fn sample(&self, x: u16, y: u16) -> u8 {
debug_assert!(
x < self.width && y < self.height,
"cannot sample mask outside of its range"
);

self.data[y as usize * self.width as usize + x as usize]
}
}
88 changes: 58 additions & 30 deletions sparse_strips/vello_api/src/paint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@

//! Types for paints.

use crate::kurbo::Affine;
use peniko::color::{AlphaColor, PremulRgba8, Srgb};
use peniko::{ColorStops, GradientKind};
use crate::pixmap::Pixmap;
use alloc::sync::Arc;
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
Expand Down Expand Up @@ -34,10 +37,10 @@ impl IndexedPaint {
/// 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),
}
Expand All @@ -48,37 +51,54 @@ impl From<AlphaColor<Srgb>> for Paint {
// 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))
Comment thread
taj-p marked this conversation as resolved.
}
}

// TODO: Replace this with the peniko type, once it supports transforms.
/// A gradient.
/// An image.
#[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,
pub struct Image {
/// The underlying pixmap of the image.
pub pixmap: Arc<Pixmap>,
/// Extend mode in the horizontal direction.
pub x_extend: peniko::Extend,
/// Extend mode in the vertical direction.
pub y_extend: peniko::Extend,
/// Hint for desired rendering quality.
pub quality: ImageQuality,
}

/// A premultiplied color.
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct PremulColor {
premul_u8: PremulRgba8,
premul_f32: peniko::color::PremulColor<Srgb>,
}

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
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,
Comment thread
taj-p marked this conversation as resolved.
}
}

/// 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. doesn't have transparency).
pub fn is_opaque(&self) -> bool {
self.premul_f32.components[3] == 1.0
}
}

Expand All @@ -89,6 +109,8 @@ pub enum PaintType {
Solid(AlphaColor<Srgb>),
/// A gradient.
Gradient(Gradient),
/// An image.
Image(Image),
}

impl From<AlphaColor<Srgb>> for PaintType {
Expand All @@ -102,3 +124,9 @@ impl From<Gradient> for PaintType {
Self::Gradient(value)
}
}

impl From<Image> for PaintType {
fn from(value: Image) -> Self {
Self::Image(value)
}
}
Loading