Skip to content

Commit

Permalink
Encode valid empty paths for empty clips (#651)
Browse files Browse the repository at this point in the history
There was logic to encode a valid empty path when the clip path was
empty, but this logic was ineffective because of empty line segment
culling. This patch directly encodes an empty path (single zero-length
line).

Also includes a doc improvement for `push_layer`.

Fixes #644
  • Loading branch information
raphlinus authored Aug 12, 2024
1 parent d1ace99 commit a00cd9f
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 2 deletions.
15 changes: 13 additions & 2 deletions vello/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ impl Scene {
/// until the layer is popped.
///
/// **However, the transforms are *not* saved or modified by the layer stack.**
///
/// Clip layers (`blend` = [`Mix::Clip`]) should have an alpha value of 1.0.
/// For an opacity group with non-unity alpha, specify [`Mix::Normal`].
pub fn push_layer(
&mut self,
blend: impl Into<BlendMode>,
Expand All @@ -74,14 +77,22 @@ impl Scene {
clip: &impl Shape,
) {
let blend = blend.into();
if blend.mix == Mix::Clip && alpha != 1.0 {
log::warn!("Clip mix mode used with semitransparent alpha");
}
let t = Transform::from_kurbo(&transform);
self.encoding.encode_transform(t);
self.encoding.encode_fill_style(Fill::NonZero);
if !self.encoding.encode_shape(clip, true) {
// If the layer shape is invalid, encode a valid empty path. This suppresses
// all drawing until the layer is popped.
self.encoding
.encode_shape(&Rect::new(0.0, 0.0, 0.0, 0.0), true);
self.encoding.encode_empty_shape();
#[cfg(feature = "bump_estimate")]
{
use peniko::kurbo::PathEl;
let path = [PathEl::MoveTo(Point::ZERO), PathEl::LineTo(Point::ZERO)];
self.estimator.count_path(path.into_iter(), &t, None);
}
} else {
#[cfg(feature = "bump_estimate")]
self.estimator.count_path(clip.path_elements(0.1), &t, None);
Expand Down
11 changes: 11 additions & 0 deletions vello_encoding/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,17 @@ impl Encoding {
encoder.finish(true) != 0
}

/// Encode an empty path.
///
/// This is useful for bookkeeping when a path is absolutely required (for example in
/// pushing a clip layer). It is almost always the case, however, that an application
/// can be optimized to not use this method.
pub fn encode_empty_shape(&mut self) {
let mut encoder = self.encode_path(true);
encoder.empty_path();
encoder.finish(true);
}

/// Encodes a path element iterator. If `is_fill` is true, all subpaths will be automatically
/// closed. Returns true if a non-zero number of segments were encoded.
pub fn encode_path_elements(
Expand Down
9 changes: 9 additions & 0 deletions vello_encoding/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,15 @@ impl<'a> PathEncoder<'a> {
self.n_encoded_segments += 1;
}

/// Encodes an empty path (as placeholder for begin clip).
pub(crate) fn empty_path(&mut self) {
let coords = [0.0f32, 0., 0., 0.];
let bytes = bytemuck::bytes_of(&coords);
self.data.extend_from_slice(bytes);
self.tags.push(PathTag::LINE_TO_F32);
self.n_encoded_segments += 1;
}

/// Closes the current subpath.
pub fn close(&mut self) {
match self.state {
Expand Down

0 comments on commit a00cd9f

Please sign in to comment.