Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions sparse_strips/vello_bench/src/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,27 @@ fn load_flower_image() -> ImageSource {
let height = image.height();
let rgba_data = image.into_rgba8().into_vec();

let mut may_have_opacities = false;
#[expect(
clippy::cast_possible_truncation,
reason = "Image dimensions fit in u16"
)]
let pixmap = Pixmap::from_parts(
let pixmap = Pixmap::from_parts_with_opacity(
rgba_data
.chunks_exact(4)
.map(|rgba| {
AlphaColor::from_rgba8(rgba[0], rgba[1], rgba[2], rgba[3])
let alpha = rgba[3];
if alpha != 255 {
may_have_opacities = true;
}
AlphaColor::from_rgba8(rgba[0], rgba[1], rgba[2], alpha)
.premultiply()
.to_rgba8()
})
.collect(),
width as u16,
height as u16,
may_have_opacities,
);

ImageSource::Pixmap(Arc::new(pixmap))
Expand Down
2 changes: 1 addition & 1 deletion sparse_strips/vello_common/src/coarse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ impl<const MODE: u8> Wide<MODE> {
Paint::Solid(s) if s.is_opaque() => FillHint::OpaqueSolid(*s),
Paint::Indexed(idx) => {
if let Some(EncodedPaint::Image(img)) = encoded_paints.get(idx.index())
&& !img.has_opacities
&& !img.may_have_opacities
&& img.sampler.alpha == 1.0
{
FillHint::OpaqueImage
Expand Down
33 changes: 16 additions & 17 deletions sparse_strips/vello_common/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl EncodeExt for Gradient {
return paint;
}

let mut has_opacities = self.stops.iter().any(|s| s.color.components[3] != 1.0);
let mut may_have_opacities = self.stops.iter().any(|s| s.color.components[3] != 1.0);

let mut base_transform;

Expand Down Expand Up @@ -153,7 +153,7 @@ impl EncodeExt for Gradient {
// alpha-compositing in case the radial gradient is undefined in certain positions,
// in which case the resulting color will be transparent and thus the gradient overall
// must be treated as non-opaque.
has_opacities |= radial_kind.has_undefined();
may_have_opacities |= radial_kind.has_undefined();

EncodedKind::Radial(radial_kind)
}
Expand Down Expand Up @@ -222,7 +222,7 @@ impl EncodeExt for Gradient {
y_advance,
ranges,
extend: self.extend,
has_opacities,
may_have_opacities,
u8_lut: OnceCell::new(),
f32_lut: OnceCell::new(),
};
Expand Down Expand Up @@ -498,21 +498,20 @@ impl EncodeExt for Image {
let (x_advance, y_advance) = x_y_advances(&transform);

let encoded = match &self.image {
ImageSource::Pixmap(pixmap) => {
EncodedImage {
source: ImageSource::Pixmap(pixmap.clone()),
sampler,
// While we could optimize RGB8 images, it's probably not worth the trouble.
has_opacities: true,
transform,
x_advance,
y_advance,
}
}
ImageSource::Pixmap(pixmap) => EncodedImage {
source: ImageSource::Pixmap(pixmap.clone()),
sampler,
may_have_opacities: pixmap.may_have_opacities(),
transform,
x_advance,
y_advance,
},
ImageSource::OpaqueId(image) => EncodedImage {
source: ImageSource::OpaqueId(*image),
sampler,
has_opacities: true,
// Safe fallback: we don't have access to pixel data for externally
// registered images, so we conservatively assume they have opacities.
may_have_opacities: true,
transform,
x_advance,
y_advance,
Expand Down Expand Up @@ -556,7 +555,7 @@ pub struct EncodedImage {
/// Sampler
pub sampler: ImageSampler,
/// Whether the image has opacities.
pub has_opacities: bool,
pub may_have_opacities: bool,
/// A transform to apply to the image.
pub transform: Affine,
/// The advance in image coordinates for one step in the x direction.
Expand Down Expand Up @@ -735,7 +734,7 @@ pub struct EncodedGradient {
/// The extend of the gradient.
pub extend: Extend,
/// Whether the gradient requires `source_over` compositing.
pub has_opacities: bool,
pub may_have_opacities: bool,
u8_lut: OnceCell<GradientLut<u8>>,
f32_lut: OnceCell<GradientLut<f32>>,
}
Expand Down
95 changes: 88 additions & 7 deletions sparse_strips/vello_common/src/pixmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,25 @@ pub struct Pixmap {
height: u16,
/// Buffer of the pixmap in RGBA8 format.
buf: Vec<PremulRgba8>,
/// Whether the pixmap may have non-opaque pixels.
///
/// Note: This may become stale if pixels are modified via [`data_mut()`](Self::data_mut),
/// [`data_as_u8_slice_mut()`](Self::data_as_u8_slice_mut), or [`set_pixel()`](Self::set_pixel).
may_have_opacities: bool,
}

impl Pixmap {
/// Create a new pixmap with the given width and height in pixels.
///
/// All pixels are initialized to transparent black.
pub fn new(width: u16, height: u16) -> Self {
let buf = vec![PremulRgba8::from_u32(0); width as usize * height as usize];
Self { width, height, buf }
Self {
width,
height,
buf,
may_have_opacities: true,
}
}

/// Create a new pixmap with the given premultiplied RGBA8 data.
Expand All @@ -36,10 +48,35 @@ impl Pixmap {
///
/// The pixels are in row-major order.
///
/// This assumes the image may have transparent pixels. Use
/// [`from_parts_with_opacity`](Self::from_parts_with_opacity) if you already
/// know the opacity status to enable optimizations.
///
/// # Panics
///
/// Panics if the `data` vector is not of length `width * height`.
pub fn from_parts(data: Vec<PremulRgba8>, width: u16, height: u16) -> Self {
Self::from_parts_with_opacity(data, width, height, true)
}

/// Create a new pixmap with the given premultiplied RGBA8 data and precomputed opacity flag.
///
/// The `data` vector must be of length `width * height` exactly.
///
/// The pixels are in row-major order.
///
/// Use this when you've already determined whether the data contains
/// non-opaque pixels to avoid redundant scanning.
///
/// # Panics
///
/// Panics if the `data` vector is not of length `width * height`.
pub fn from_parts_with_opacity(
data: Vec<PremulRgba8>,
width: u16,
height: u16,
may_have_opacities: bool,
) -> Self {
assert_eq!(
data.len(),
usize::from(width) * usize::from(height),
Expand All @@ -49,6 +86,7 @@ impl Pixmap {
width,
height,
buf: data,
may_have_opacities,
}
}

Expand All @@ -59,12 +97,14 @@ impl Pixmap {
/// black. If the pixmap buffer is larger than required, the buffer is truncated and its
/// reserved capacity is unchanged.
pub fn resize(&mut self, width: u16, height: u16) {
let new_len = usize::from(width) * usize::from(height);
// If we're growing, new pixels are transparent black
if new_len > self.buf.len() {
self.may_have_opacities = true;
}
self.width = width;
self.height = height;
self.buf.resize(
usize::from(width) * usize::from(height),
PremulRgba8::from_u32(0),
);
self.buf.resize(new_len, PremulRgba8::from_u32(0));
}

/// Shrink the capacity of the pixmap buffer to fit the pixmap's current size.
Expand All @@ -90,6 +130,36 @@ impl Pixmap {
self.height
}

/// Returns whether the pixmap may have non-opaque pixels.
///
/// This value is computed at construction time. It may become stale if pixels are
/// modified directly via [`data_mut()`](Self::data_mut),
/// [`data_as_u8_slice_mut()`](Self::data_as_u8_slice_mut), or [`set_pixel()`](Self::set_pixel).
///
/// Use [`set_may_have_opacities()`](Self::set_may_have_opacities) to manually update the flag,
/// or [`recompute_may_have_opacities()`](Self::recompute_may_have_opacities) to recalculate it
/// by scanning all pixels.
pub fn may_have_opacities(&self) -> bool {
self.may_have_opacities
}

/// Manually set the `may_have_opacities` flag.
///
/// Use this after modifying pixels via [`data_mut()`](Self::data_mut) or
/// [`set_pixel()`](Self::set_pixel) when you know whether the image has
/// non-opaque pixels.
pub fn set_may_have_opacities(&mut self, may_have_opacities: bool) {
self.may_have_opacities = may_have_opacities;
}

/// Recalculate `may_have_opacities` by scanning all pixels.
///
/// Use this after modifying pixels via [`data_mut()`](Self::data_mut) or
/// [`set_pixel()`](Self::set_pixel) when you need accurate opacity information.
pub fn recompute_may_have_opacities(&mut self) {
self.may_have_opacities = self.buf.iter().any(|pixel| pixel.a != 255);
}

/// Apply an alpha value to the whole pixmap.
pub fn multiply_alpha(&mut self, alpha: u8) {
#[expect(
Expand All @@ -106,6 +176,11 @@ impl Pixmap {
a: multiply(pixel.a),
};
}

// If we applied a non-opaque alpha, the image now has opacities
if alpha != 255 {
self.may_have_opacities = true;
}
}

/// Create a pixmap from a PNG file.
Expand Down Expand Up @@ -177,17 +252,23 @@ impl Pixmap {
}
};

let mut may_have_opacities = false;
for pixel in pixmap.data_mut() {
let alpha = u16::from(pixel.a);
let alpha = pixel.a;
if alpha != 255 {
may_have_opacities = true;
}
let alpha_u16 = u16::from(alpha);
#[expect(
clippy::cast_possible_truncation,
reason = "Overflow should be impossible."
)]
let premultiply = |e: u8| ((u16::from(e) * alpha) / 255) as u8;
let premultiply = |e: u8| ((u16::from(e) * alpha_u16) / 255) as u8;
pixel.r = premultiply(pixel.r);
pixel.g = premultiply(pixel.g);
pixel.b = premultiply(pixel.b);
}
pixmap.may_have_opacities = may_have_opacities;

Ok(pixmap)
}
Expand Down
18 changes: 9 additions & 9 deletions sparse_strips/vello_cpu/src/fine/lowp/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use crate::fine::PosExt;
use crate::fine::common::image::{ImagePainterData, extend, sample};
use crate::fine::macros::u8x16_painter;
use vello_common::encode::EncodedImage;
use vello_common::fearless_simd::{Simd, SimdBase, f32x4, u8x16};
use vello_common::fearless_simd::{Simd, SimdBase, SimdFloat, f32x4, u8x16, u16x16};
use vello_common::pixmap::Pixmap;
use vello_common::simd::element_wise_splat;
use vello_common::util::f32_to_u8;
use vello_common::util::{Div255Ext, f32_to_u8};

/// A faster bilinear image renderer for the u8 pipeline.
#[derive(Debug)]
Expand Down Expand Up @@ -78,17 +78,17 @@ impl<S: Simd> Iterator for BilinearImagePainter<'_, S> {

let fx = f32_to_u8(element_wise_splat(
self.simd,
fract(x_positions + 0.5) * 256.0,
fract(x_positions + 0.5).madd(255.0, 0.5),
));
let fy = f32_to_u8(element_wise_splat(
self.simd,
fract(y_positions + 0.5) * 256.0,
fract(y_positions + 0.5).madd(255.0, 0.5),
));
let fx_inv = self.simd.widen_u8x16(u8x16::splat(self.simd, 255) - fx);
let fy_inv = self.simd.widen_u8x16(u8x16::splat(self.simd, 255) - fy);

let fx = self.simd.widen_u8x16(fx);
let fy = self.simd.widen_u8x16(fy);
let fx_inv = u16x16::splat(self.simd, 255) - fx;
let fy_inv = u16x16::splat(self.simd, 255) - fy;

let x_pos1 = extend_x(x_positions - 0.5);
let x_pos2 = extend_x(x_positions + 0.5);
Expand All @@ -108,9 +108,9 @@ impl<S: Simd> Iterator for BilinearImagePainter<'_, S> {
.simd
.widen_u8x16(sample(self.simd, &self.data, x_pos2, y_pos2));

let ip1 = (p00 * fx_inv + p10 * fx) >> 8;
let ip2 = (p01 * fx_inv + p11 * fx) >> 8;
let res = self.simd.narrow_u16x16((ip1 * fy_inv + ip2 * fy) >> 8);
let ip1 = (p00 * fx_inv + p10 * fx).div_255();
let ip2 = (p01 * fx_inv + p11 * fx).div_255();
let res = self.simd.narrow_u16x16((ip1 * fy_inv + ip2 * fy).div_255());

self.data.cur_pos += self.data.image.x_advance;

Expand Down
Loading