Encode valid empty paths for empty clips (#651)
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
diff --git a/vello/src/scene.rs b/vello/src/scene.rs
index 103e9b2..92aa373 100644
--- a/vello/src/scene.rs
+++ b/vello/src/scene.rs
@@ -66,6 +66,9 @@
/// 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>,
@@ -74,14 +77,22 @@
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);
diff --git a/vello_encoding/src/encoding.rs b/vello_encoding/src/encoding.rs
index b83ec5a..8cf6aff 100644
--- a/vello_encoding/src/encoding.rs
+++ b/vello_encoding/src/encoding.rs
@@ -240,6 +240,17 @@
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(
diff --git a/vello_encoding/src/path.rs b/vello_encoding/src/path.rs
index 7bb18cb..c4c2fa5 100644
--- a/vello_encoding/src/path.rs
+++ b/vello_encoding/src/path.rs
@@ -599,6 +599,15 @@
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 {