mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 02:07:09 -06:00
Merge pull request #2781 from dotCooCoo/fix/grid-images-count-decode-failures
Render: raster exports warn about an undecodable image drawn from a kept Rasterize image
This commit is contained in:
@@ -6,6 +6,7 @@ use vectorcraft_doc::pattern::{PATTERN_EDIT_LAYER, RepeatKind, TileType};
|
||||
use vectorcraft_doc::{Node, NodeKind};
|
||||
use vectorcraft_geom::{Point, Rect};
|
||||
|
||||
use super::tests_menucmds::place_undecodable_png;
|
||||
use super::*;
|
||||
|
||||
fn session() -> Session {
|
||||
@@ -400,6 +401,37 @@ fn every_raster_export_says_when_a_pattern_holds_an_image_that_could_not_be_deco
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_raster_export_says_when_a_pattern_tile_draws_an_undecodable_image_from_its_kept_rasterize_image() {
|
||||
// A PNG whose pixel data is cut short, with Rasterize, a drop shadow and a blur: the shadow
|
||||
// draws the Rasterize image, and the blurred content draws the image kept from that draw.
|
||||
let mut s = session();
|
||||
let placed = place_undecodable_png(&mut s);
|
||||
for (effect, params) in [
|
||||
("rasterize", json!({"background": "white"})),
|
||||
("stylize.dropShadow", json!({"x": 2, "y": 2, "blur": 1})),
|
||||
("blur.gaussian", json!({"radius": 4})),
|
||||
] {
|
||||
s.execute("effect.apply", &json!({"effect": effect, "params": params, "ids": [placed.0]})).unwrap();
|
||||
}
|
||||
sel(&mut s, &[placed]);
|
||||
s.execute("object.pattern.make", &json!({"name": "Cut", "width": 80, "height": 40})).unwrap();
|
||||
s.execute("object.pattern.done", &json!({})).unwrap();
|
||||
sel(&mut s, &[placed]);
|
||||
s.execute("edit.clear", &json!({})).unwrap();
|
||||
let big = rect(&mut s, 100.0, 100.0, 50.0, 50.0);
|
||||
s.execute("paint.setFill", &json!({"ids": [big.0], "swatch": "Cut"})).unwrap();
|
||||
let warns = |s: &mut Session| {
|
||||
let r = s.execute("document.export", &json!({"format": "png", "background": "transparent"})).unwrap();
|
||||
r["warnings"].as_array().is_some_and(|w| w.iter().any(|w| w.as_str().is_some_and(|w| w.contains("couldn't be decoded"))))
|
||||
};
|
||||
assert!(warns(&mut s), "the first export");
|
||||
// A new overlap draws a new tile of the same art, and the tile renderer draws its shadow and
|
||||
// its blurred content from the rasters it kept.
|
||||
s.execute("pattern.options", &json!({"name": "Cut", "overlap": {"h": "right"}})).unwrap();
|
||||
assert!(warns(&mut s), "the export after Pattern Options");
|
||||
}
|
||||
|
||||
// ---------- repeat ----------
|
||||
|
||||
#[test]
|
||||
|
||||
+42
-4
@@ -144,8 +144,10 @@ struct Kept {
|
||||
|
||||
#[derive(Clone)]
|
||||
enum KeptImage {
|
||||
/// The image on its own grid, as [`Renderer::rasterize_image`] makes it.
|
||||
Grid(std::rc::Rc<(Vec<u8>, usize, usize)>),
|
||||
/// The image on its own grid, as [`Renderer::rasterize_image`] makes it, and how many images
|
||||
/// in the content failed to decode while it was made ([`crate::paint::decode_failures`]):
|
||||
/// each draw of the image from here counts them again ([`crate::paint::add_decode_failures`]).
|
||||
Grid(std::rc::Rc<(Vec<u8>, usize, usize)>, u64),
|
||||
/// An image over [`Renderer::rasterize_image`]'s limits: the content's raster with the
|
||||
/// Rasterize applied, as the first draw made it, and the transform from its pixels to
|
||||
/// document space.
|
||||
@@ -1757,13 +1759,17 @@ impl Renderer {
|
||||
Some(RasterFx::Rasterize(r)) => {
|
||||
let rect = r.image(content.bounds.unwrap_or(content.reach), line);
|
||||
let kept = content.kept.and_then(|k| match &*k.image.borrow() {
|
||||
Some(KeptImage::Grid(image)) => Some(image.clone()),
|
||||
Some(KeptImage::Grid(image, failed)) => {
|
||||
crate::paint::add_decode_failures(*failed);
|
||||
Some(image.clone())
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let image = kept.or_else(|| {
|
||||
let failures = crate::paint::decode_failures();
|
||||
let image = self.rasterize_image(f, r, rect, paint).map(std::rc::Rc::new);
|
||||
if let (Some(k), Some(image)) = (content.kept, &image) {
|
||||
*k.image.borrow_mut() = Some(KeptImage::Grid(image.clone()));
|
||||
*k.image.borrow_mut() = Some(KeptImage::Grid(image.clone(), crate::paint::decode_failures() - failures));
|
||||
}
|
||||
image
|
||||
});
|
||||
@@ -2757,6 +2763,38 @@ mod tests {
|
||||
assert_eq!(r.pixel_fx.len(), 1, "the later frames draw from the raster kept");
|
||||
}
|
||||
|
||||
/// An image that can't be decoded, with Rasterize, a drop shadow and a blur: the shadow draws
|
||||
/// the Rasterize image first, and the blurred content draws the image kept from that draw
|
||||
/// ([`super::Kept`]). Every frame counts the failure, also when the shadow and the blurred
|
||||
/// content are drawn from the rasters kept.
|
||||
#[test]
|
||||
fn rasters_drawn_from_a_kept_rasterize_image_count_the_images_it_could_not_decode() {
|
||||
use vectorcraft_doc::{ImageBlob, ImageObject, NodeKind};
|
||||
let mut d = Document::new(100.0, 100.0);
|
||||
// A PNG signature with nothing after it.
|
||||
d.images.insert("cut".into(), ImageBlob::new("image/png", b"\x89PNG\r\n\x1a\n".to_vec()));
|
||||
let im =
|
||||
ImageObject { key: "cut".into(), width: 40, height: 40, xf: Affine::translate((30.0, 30.0)), link: None, placement: Default::default() };
|
||||
let mut n = Node::new(d.alloc_id(), NodeKind::Image(Box::new(im)));
|
||||
n.appearance.effects = [
|
||||
("rasterize", json!({"background": "white"})),
|
||||
("stylize.dropShadow", json!({"mode": "normal", "opacity": 100, "x": 2, "y": 2, "blur": 1, "color": "#0000ff"})),
|
||||
("blur.gaussian", json!({"radius": 4.0})),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(id, params)| Effect { id: id.into(), params, visible: true })
|
||||
.collect();
|
||||
d.insert(Some(d.layers[0].id), 0, n).unwrap();
|
||||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() };
|
||||
let mut r = Renderer::new();
|
||||
for frame in 0..3 {
|
||||
let before = crate::paint::decode_failures();
|
||||
r.render(&d, 100, 100, Affine::IDENTITY, &opts);
|
||||
assert!(crate::paint::decode_failures() > before, "frame {frame}");
|
||||
}
|
||||
assert_eq!((r.shadows.len(), r.pixel_fx.len()), (1, 1), "the later frames draw from the rasters kept");
|
||||
}
|
||||
|
||||
/// While interacting, an object that only moved draws its drop shadow and filtered content
|
||||
/// from the rasters of an earlier frame, at the nearest whole pixel, without running the
|
||||
/// effects again (#1257). The frame after the interaction runs them again: it is what a fresh
|
||||
|
||||
Reference in New Issue
Block a user