Skip to content
Draft
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
83 changes: 27 additions & 56 deletions sparse_strips/vello_common/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use crate::color::palette::css::BLACK;
use crate::color::{ColorSpaceTag, HueDirection, Srgb, gradient};
use crate::kurbo::{Affine, Point, Vec2};
use crate::math::{FloatExt, compute_erf7};
use crate::paint::{Image, ImageSource, IndexedPaint, Paint, PremulColor};
use crate::peniko::{ColorStop, ColorStops, Extend, Gradient, GradientKind, ImageQuality};
use crate::paint::{Image, ImageSource, IndexedPaint, Paint, PremulColor, encode_image_with};
use crate::peniko::{ColorStop, ColorStops, Extend, Gradient, GradientKind};
use alloc::borrow::Cow;
use alloc::fmt::Debug;
use alloc::vec;
Expand Down Expand Up @@ -429,60 +429,7 @@ impl private::Sealed for Image {}

impl EncodeExt for Image {
fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
let idx = paints.len();

let mut sampler = self.sampler;

if sampler.alpha != 1.0 {
// If the sampler alpha is not 1.0, we need to force alpha compositing.
unimplemented!("Applying opacity to image commands");
}

let c = transform.as_coeffs();

// Optimize image quality for integer-only translations.
if (c[0] as f32 - 1.0).is_nearly_zero()
&& (c[1] as f32).is_nearly_zero()
&& (c[2] as f32).is_nearly_zero()
&& (c[3] as f32 - 1.0).is_nearly_zero()
&& ((c[4] - c[4].floor()) as f32).is_nearly_zero()
&& ((c[5] - c[5].floor()) as f32).is_nearly_zero()
&& sampler.quality == ImageQuality::Medium
{
sampler.quality = ImageQuality::Low;
}

// Similarly to gradients, apply a 0.5 offset so we sample at the center of
// a pixel.
let transform = transform.inverse() * Affine::translate((0.5, 0.5));

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::OpaqueId(image) => EncodedImage {
source: ImageSource::OpaqueId(*image),
sampler,
has_opacities: true,
transform,
x_advance,
y_advance,
},
};

paints.push(EncodedPaint::Image(encoded));

Paint::Indexed(IndexedPaint::new(idx))
encode_image_with(self, paints, transform, false)
}
}

Expand Down Expand Up @@ -524,6 +471,30 @@ pub struct EncodedImage {
pub x_advance: Vec2,
/// The advance in image coordinates for one step in the y direction.
pub y_advance: Vec2,
/// Whether the extends in `ImageSampler` should be ignored.
pub ignore_extend: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I have a similar task in my backlog 😅 In some cases like the one below, we’d prefer to render transparent pixels instead of using Extend::Pad. I think the more appropriate solution would be to add another extend Transparent here. What do you think?

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, but unfortunately we just cut a new peniko release yesterday. :/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem! We can include it in one of the next releases.

}

impl EncodedImage {
/// Return the x extend of the image.
#[inline(always)]
pub fn x_extend(&self) -> Option<peniko::Extend> {
if self.ignore_extend {
None
} else {
Some(self.sampler.x_extend)
}
}

/// Return the y extend of the image.
#[inline(always)]
pub fn y_extend(&self) -> Option<peniko::Extend> {
if self.ignore_extend {
None
} else {
Some(self.sampler.y_extend)
}
}
}

/// Computed properties of a linear gradient.
Expand Down
74 changes: 73 additions & 1 deletion sparse_strips/vello_common/src/paint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@

//! Types for paints.

use crate::encode::{EncodedImage, EncodedPaint, x_y_advances};
use crate::kurbo::Affine;
use crate::math::FloatExt;
use crate::pixmap::Pixmap;
use alloc::sync::Arc;
use alloc::vec::Vec;
use peniko::{
Gradient,
Gradient, ImageQuality,
color::{AlphaColor, PremulRgba8, Srgb},
};

#[cfg(not(feature = "std"))]
use peniko::kurbo::common::FloatFuncs as _;

/// A paint that needs to be resolved via its index.
// In the future, we might add additional flags, that's why we have
// this thin wrapper around u32, so we can change the underlying
Expand Down Expand Up @@ -139,6 +146,71 @@ impl ImageSource {
/// An image.
pub type Image = peniko::ImageBrush<ImageSource>;

/// Encode the image, optionally ignoring the extends of the image.
pub fn encode_image_with(
image: &Image,
paints: &mut Vec<EncodedPaint>,
transform: Affine,
ignore_extend: bool,
) -> Paint {
let idx = paints.len();

let mut sampler = image.sampler;

if sampler.alpha != 1.0 {
// If the sampler alpha is not 1.0, we need to force alpha compositing.
unimplemented!("Applying opacity to image commands");
}

let c = transform.as_coeffs();

// Optimize image quality for integer-only translations.
if (c[0] as f32 - 1.0).is_nearly_zero()
&& (c[1] as f32).is_nearly_zero()
&& (c[2] as f32).is_nearly_zero()
&& (c[3] as f32 - 1.0).is_nearly_zero()
&& ((c[4] - c[4].floor()) as f32).is_nearly_zero()
&& ((c[5] - c[5].floor()) as f32).is_nearly_zero()
&& sampler.quality == ImageQuality::Medium
{
sampler.quality = ImageQuality::Low;
}

// Similarly to gradients, apply a 0.5 offset so we sample at the center of
// a pixel.
let transform = transform.inverse() * Affine::translate((0.5, 0.5));

let (x_advance, y_advance) = x_y_advances(&transform);

let encoded = match &image.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,
ignore_extend,
}
}
ImageSource::OpaqueId(image) => EncodedImage {
source: ImageSource::OpaqueId(*image),
sampler,
has_opacities: true,
transform,
x_advance,
y_advance,
ignore_extend,
},
};

paints.push(EncodedPaint::Image(encoded));

Paint::Indexed(IndexedPaint::new(idx))
}

/// A premultiplied color.
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct PremulColor {
Expand Down
47 changes: 33 additions & 14 deletions sparse_strips/vello_cpu/src/fine/common/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use vello_common::fearless_simd::{Bytes, Simd, SimdBase, SimdFloat, f32x4, f32x1
use vello_common::pixmap::Pixmap;
use vello_common::simd::element_wise_splat;

const PIXEL_OOB_MARKER: f32 = -1.0;

/// A painter for nearest-neighbor images with no skewing.
#[derive(Debug)]
pub(crate) struct PlainNNImagePainter<'a, S: Simd> {
Expand Down Expand Up @@ -38,7 +40,7 @@ impl<'a, S: Simd> PlainNNImagePainter<'a, S> {
data.x_advances.1,
data.y_advances.1,
),
image.sampler.y_extend,
image.y_extend(),
data.height,
data.height_inv,
);
Expand Down Expand Up @@ -68,7 +70,7 @@ impl<S: Simd> Iterator for PlainNNImagePainter<'_, S> {
let x_pos = extend(
self.simd,
self.cur_x_pos,
self.data.image.sampler.x_extend,
self.data.image.x_extend(),
self.data.width,
self.data.width_inv,
);
Expand Down Expand Up @@ -116,7 +118,7 @@ impl<S: Simd> Iterator for NNImagePainter<'_, S> {
self.data.x_advances.0,
self.data.y_advances.0,
),
self.data.image.sampler.x_extend,
self.data.image.x_extend(),
self.data.width,
self.data.width_inv,
);
Expand All @@ -129,7 +131,7 @@ impl<S: Simd> Iterator for NNImagePainter<'_, S> {
self.data.x_advances.1,
self.data.y_advances.1,
),
self.data.image.sampler.y_extend,
self.data.image.y_extend(),
self.data.height,
self.data.height_inv,
);
Expand Down Expand Up @@ -214,7 +216,7 @@ impl<S: Simd> Iterator for FilteredImagePainter<'_, S> {
extend(
self.simd,
x_positions + $offsets[$idx],
self.data.image.sampler.y_extend,
self.data.image.x_extend(),
self.data.width,
self.data.width_inv,
)
Expand All @@ -226,7 +228,7 @@ impl<S: Simd> Iterator for FilteredImagePainter<'_, S> {
extend(
self.simd,
y_positions + $offsets[$idx],
self.data.image.sampler.y_extend,
self.data.image.y_extend(),
self.data.height,
self.data.height_inv,
)
Expand Down Expand Up @@ -335,6 +337,7 @@ pub(crate) struct ImagePainterData<'a, S: Simd> {
pub(crate) pixmap: &'a Pixmap,
pub(crate) x_advances: (f32, f32),
pub(crate) y_advances: (f32, f32),
pub(crate) mask_oob: bool,
pub(crate) height: f32x4<S>,
pub(crate) height_inv: f32x4<S>,
pub(crate) width: f32x4<S>,
Expand Down Expand Up @@ -362,11 +365,13 @@ impl<'a, S: Simd> ImagePainterData<'a, S> {

let x_advances = (image.x_advance.x as f32, image.x_advance.y as f32);
let y_advances = (image.y_advance.x as f32, image.y_advance.y as f32);
let mask_oob = image.ignore_extend;

Self {
cur_pos: start_pos,
pixmap,
x_advances,
mask_oob,
y_advances,
image,
width,
Expand All @@ -387,23 +392,33 @@ pub(crate) fn sample<S: Simd>(
) -> u8x16<S> {
let idx = x_positions.cvt_u32() + y_positions.cvt_u32() * data.width_u32;

u32x4::from_slice(
let mut res = u32x4::from_slice(
simd,
&[
data.pixmap.sample_idx(idx[0]).to_u32(),
data.pixmap.sample_idx(idx[1]).to_u32(),
data.pixmap.sample_idx(idx[2]).to_u32(),
data.pixmap.sample_idx(idx[3]).to_u32(),
],
)
.reinterpret_u8()
);

if data.mask_oob {
let mask = simd.or_mask32x4(
x_positions.simd_eq(f32x4::splat(simd, PIXEL_OOB_MARKER)),
y_positions.simd_eq(f32x4::splat(simd, PIXEL_OOB_MARKER)),
);

res = simd.select_u32x4(mask, u32x4::splat(simd, 0), res);
}

res.reinterpret_u8()
}

#[inline(always)]
pub(crate) fn extend<S: Simd>(
simd: S,
val: f32x4<S>,
extend: crate::peniko::Extend,
extend: Option<crate::peniko::Extend>,
max: f32x4<S>,
inv_max: f32x4<S>,
) -> f32x4<S> {
Expand All @@ -414,13 +429,13 @@ pub(crate) fn extend<S: Simd>(
match extend {
// Note that max should be exclusive, so subtract a small bias to enforce that.
// Otherwise, we might sample out-of-bounds pixels.
crate::peniko::Extend::Pad => val.min(max - bias).max(f32x4::splat(simd, 0.0)),
crate::peniko::Extend::Repeat => val
Some(crate::peniko::Extend::Pad) => val.min(max - bias).max(f32x4::splat(simd, 0.0)),
Some(crate::peniko::Extend::Repeat) => val
.msub((val * inv_max).floor(), max)
// In certain edge cases, we might still end up with a higher number.
.min(max - 1.0),
// <https://github.com/google/skia/blob/220738774f7a0ce4a6c7bd17519a336e5e5dea5b/src/opts/SkRasterPipeline_opts.h#L3274-L3290>
crate::peniko::Extend::Reflect => {
Some(crate::peniko::Extend::Reflect) => {
let u = val
- (val * inv_max * f32x4::splat(simd, 0.5)).floor() * f32x4::splat(simd, 2.0) * max;
let s = (u * inv_max).floor();
Expand All @@ -438,6 +453,10 @@ pub(crate) fn extend<S: Simd>(
// In certain edge cases, we might still end up with a higher number.
.min(max - 1.0)
}
None => {
let mask = simd.and_mask32x4(val.simd_ge(f32x4::splat(simd, 0.0)), val.simd_lt(max));
simd.select_f32x4(mask, val, f32x4::splat(simd, PIXEL_OOB_MARKER))
}
}
}

Expand Down Expand Up @@ -546,7 +565,7 @@ mod tests {
let max_inv = 1.0 / max;

let num = f32x4::splat(simd, 127.00001);
let res = extend(simd, num, crate::peniko::Extend::Repeat, max, max_inv);
let res = extend(simd, num, Some(crate::peniko::Extend::Repeat), max, max_inv);

assert!(res[0] <= 127.0);
}
Expand Down
4 changes: 2 additions & 2 deletions sparse_strips/vello_cpu/src/fine/lowp/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<S: Simd> Iterator for BilinearImagePainter<'_, S> {
extend(
self.simd,
x_pos,
self.data.image.sampler.x_extend,
self.data.image.x_extend(),
self.data.width,
self.data.width_inv,
)
Expand All @@ -70,7 +70,7 @@ impl<S: Simd> Iterator for BilinearImagePainter<'_, S> {
extend(
self.simd,
y_pos,
self.data.image.sampler.y_extend,
self.data.image.y_extend(),
self.data.height,
self.data.height_inv,
)
Expand Down
Loading