mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 00:09:41 +00:00
.ai export: the masked pieces of a warped image no longer hide one another
A raster mesh warp with Preserve Shape Using Transparency cuts an image into pieces with opacity masks and puts them in a knockout group, each piece knocking out its triangle. The reader gives the pieces no such mark, so each knocked out all that its image paints without its mask, and the file opened with most of the image hidden. The group of such pieces is now written without knockout. An image whose pixels are partly transparent draws its alpha twice only where neighboring pieces overlap.
This commit is contained in:
@@ -7398,16 +7398,19 @@ fn the_pieces_of_a_warped_image_are_written_as_the_parts_they_show() {
|
||||
}
|
||||
|
||||
/// The pieces of a warped image that Preserve Shape Using Transparency cuts with opacity masks that
|
||||
/// clip are written as the parts their masks show, and the file reads back drawing as it did.
|
||||
/// clip are written as the parts their masks show, in a group without knockout, and the file reads
|
||||
/// back drawing as it did.
|
||||
#[test]
|
||||
fn masked_pieces_of_a_warped_image_are_written_as_the_parts_they_show() {
|
||||
// GUARDS: each masked piece of a warped image was written with all of the image's samples.
|
||||
// GUARDS: the pieces' knockout group read back with each piece hiding the pieces below it.
|
||||
let doc = photo_doc(|d| {
|
||||
let photo = speckled_photo(d);
|
||||
let NodeKind::Image(im) = &photo.kind else { panic!("not an image") };
|
||||
let bend = |p: vectorcraft_geom::Point| vectorcraft_geom::Point::new(p.x, p.y + 12.0 * ((p.x - 20.0) / 128.0 * std::f64::consts::PI).sin());
|
||||
vectorcraft_doc::live::mesh_warp_image(&photo, im, &bend, 4, false, true)
|
||||
});
|
||||
assert_eq!(doc.layers[0].children().unwrap()[0].knockout, vectorcraft_doc::Knockout::On);
|
||||
let sizes = sample_sizes(&doc);
|
||||
assert_eq!(sizes.len(), 32, "{sizes:?}");
|
||||
assert_eq!(sizes[0], SPECKLED_WHOLE, "the first piece holds the whole image");
|
||||
@@ -7422,6 +7425,28 @@ fn masked_pieces_of_a_warped_image_are_written_as_the_parts_they_show() {
|
||||
assert!((st.bad_pixels as f64) < 0.005 * st.pixels as f64, "{} of {} pixels differ", st.bad_pixels, st.pixels);
|
||||
}
|
||||
|
||||
/// The masked pieces of a warped image whose pixels are half transparent read back drawing as they
|
||||
/// did, except where neighboring pieces overlap: there the image's alpha draws twice.
|
||||
#[test]
|
||||
fn masked_pieces_of_a_translucent_warped_image_read_back_but_for_their_overlaps() {
|
||||
// GUARDS: the pieces' knockout group read back with each piece hiding the pieces below it
|
||||
// (8,942 of 20,000 pixels differed).
|
||||
let doc = photo_doc(|d| {
|
||||
let px = image::RgbaImage::from_fn(64, 64, |x, y| image::Rgba([(x * 4) as u8, (y * 4) as u8, ((x * 7 + y * 13) % 256) as u8, 128]));
|
||||
let photo = image_node(d, ImageBlob::png(png_of(px.into())), (64, 64), Affine::new([2.0, 0.0, 0.0, 2.0, 20.0, 10.0]), None);
|
||||
let NodeKind::Image(im) = &photo.kind else { panic!("not an image") };
|
||||
let bend = |p: vectorcraft_geom::Point| vectorcraft_geom::Point::new(p.x, p.y + 12.0 * ((p.x - 20.0) / 128.0 * std::f64::consts::PI).sin());
|
||||
vectorcraft_doc::live::mesh_warp_image(&photo, im, &bend, 4, false, true)
|
||||
});
|
||||
let back = read_back(&doc);
|
||||
let st = vectorcraft_testkit::raster::perceptual_diff(
|
||||
&vectorcraft_testkit::raster::render_artboard(&doc),
|
||||
&vectorcraft_testkit::raster::render_artboard(&back),
|
||||
8.0,
|
||||
);
|
||||
assert!((st.bad_pixels as f64) < 0.05 * st.pixels as f64, "{} of {} pixels differ", st.bad_pixels, st.pixels);
|
||||
}
|
||||
|
||||
/// An image with a live Warp keeps its pixels whole once, and its drawn look's pieces are each the
|
||||
/// part of them they show: it reads back as the image with its Warp live.
|
||||
#[test]
|
||||
|
||||
@@ -648,7 +648,14 @@ impl<'a> Writer<'a> {
|
||||
}
|
||||
self.effects(n);
|
||||
self.state(n.locked, !n.visible);
|
||||
let xy = Xy::of(n);
|
||||
let mut xy = Xy::of(n);
|
||||
if masked_warp_pieces(n) {
|
||||
// The reader gives the pieces no mark, and a knockout group's member then knocks out
|
||||
// what it paints without its opacity mask: each piece would hide the pieces below it.
|
||||
// Without knockout, a partly transparent image draws its alpha twice only where the
|
||||
// pieces overlap.
|
||||
xy.knockout = Knockout::Off;
|
||||
}
|
||||
let start = self.body.len();
|
||||
match &n.kind {
|
||||
NodeKind::Layer { .. } => {}
|
||||
@@ -1659,6 +1666,16 @@ fn clip_path(n: &Node) -> Option<(PathData, FillRule)> {
|
||||
path.subpaths.iter().any(|sp| !sp.anchors.is_empty()).then_some((path, rule))
|
||||
}
|
||||
|
||||
/// Is `n` a knockout group of the pieces of a mesh-warped image cut by opacity masks
|
||||
/// ([`vectorcraft_doc::live::mesh_warp_image`] with Preserve Shape Using Transparency)? Each piece
|
||||
/// knocks out its triangle ([`Node::knockout_outline`]); read from the data, a piece knocks out all
|
||||
/// that its image paints instead.
|
||||
fn masked_warp_pieces(n: &Node) -> bool {
|
||||
n.knockout == Knockout::On
|
||||
&& matches!(&n.kind, NodeKind::Group { children, clip: false }
|
||||
if !children.is_empty() && children.iter().all(|c| c.extras().warp_piece && c.extras().mask.is_some()))
|
||||
}
|
||||
|
||||
/// `text`'s lines on `%_` lines, as a part of the data that readers of the printed form skip (an
|
||||
/// opacity mask's art, a pattern's tile) has them. An image's samples stay as they are after an
|
||||
/// `XI` without `%_`, and `%_%%EndData` ends them, as in the files users sent. Samples hidden
|
||||
|
||||
Reference in New Issue
Block a user