Render: raster exports warn about an undecodable image drawn from a kept Rasterize image

When a drop shadow or an outer glow follows Effect › Rasterize, the
renderer makes the Rasterize image on the first draw and keeps it for
the shadow and the content, which draw it again. The kept image held no
count of the images that failed to decode while it was made. With a
blur or a filter after the shadow, the content's raster was made from
the kept image and recorded no failure, so a renderer that drew the
object again took the shadow and the content from its caches and
counted nothing. Pattern tiles are drawn by a renderer that keeps these
rasters across exports: a tile drawn again with the same art, for
example after Pattern Options changes the overlap, gave an export
without the "couldn't be decoded" warning although the picture lacked
the image.

The kept Rasterize image now stores how many decodes failed while it
was made, and each draw from it adds that number to the count again, as
kept effect rasters and pattern tiles do.
This commit is contained in:
dotCooCoo
2026-10-18 12:34:28 +07:00
committed by bflatastic
parent df7daff3e4
commit 2fc47948d5
2 changed files with 74 additions and 4 deletions
+32
View File
@@ -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]