diff --git a/crates/engine/src/cmd/fileio/legacy_ai.rs b/crates/engine/src/cmd/fileio/legacy_ai.rs index 9bf7a311a..a197363b2 100644 --- a/crates/engine/src/cmd/fileio/legacy_ai.rs +++ b/crates/engine/src/cmd/fileio/legacy_ai.rs @@ -270,7 +270,9 @@ fn flattened(src: &Document, at: usize, id: NodeId) -> Result> { let pt = im.xf.determinant().abs().sqrt(); let ppi = if pt.is_finite() && pt > 0.0 { (72.0 / pt).clamp(MIN_PPI, MAX_PPI) } else { MIN_PPI }; let area = (bounds.width() * bounds.height()).max(1.0); - let scale = (ppi / 72.0).min((super::super::menucmds::MAX_PIXELS / area).sqrt()); + // No side longer than the renderer draws (rounded up to whole pixels below). + let longest = (f64::from(vectorcraft_render::MAX_RASTER_SIDE) - 2.0) / bounds.width().max(bounds.height()).max(1.0); + let scale = (ppi / 72.0).min((super::super::menucmds::MAX_PIXELS / area).sqrt()).min(longest); if !(scale.is_finite() && scale > 0.0) { return Ok(None); } diff --git a/crates/engine/src/tests/legacy_ai.rs b/crates/engine/src/tests/legacy_ai.rs index 9a9aafcba..fefed0ad1 100644 --- a/crates/engine/src/tests/legacy_ai.rs +++ b/crates/engine/src/tests/legacy_ai.rs @@ -368,6 +368,31 @@ fn a_transparent_image_is_flattened_with_the_art_under_it_which_stays() { assert_eq!(colour_at(page, 10.0, 20.0), [255, 0, 0], "the square outside the image"); } +/// A transparent image longer than the renderer draws at its own resolution (32,768 pixels a +/// side) is flattened whole, at a lower resolution. +#[test] +fn a_long_transparent_image_is_flattened_whole() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 100, "height": 4600})).unwrap(); + s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 100, "height": 4600})).unwrap(); + s.execute("paint.setFill", &json!({"color": "#ff0000"})).unwrap(); + s.execute("paint.setStroke", &json!({"none": true})).unwrap(); + let p = json!({"name": "i.png", "dataBase64": vectorcraft_format::base64_encode(&png(2000, 1600, [0, 0, 0, 128])), "at": [50, 50]}); + s.execute("file.place", &p).unwrap(); + // 10 × 4500 pt: about 600 ppi, so 37,500 pixels long. + s.execute("object.setBounds", &json!({"x": 10, "y": 10, "width": 10, "height": 4500, "reference": 0})).unwrap(); + let before = [colour_at(&s.doc().unwrap().doc, 15.0, 100.0), colour_at(&s.doc().unwrap().doc, 15.0, 4400.0)]; + assert!(before.iter().all(|c| c[0] < 200), "the image darkens the red: {before:?}"); + let r = s.execute("file.saveCopy", &json!({"format": "ai", "aiVersion": "7"})).unwrap(); + assert!(warnings(&r).iter().any(|w| w.contains("flattened with the art under them")), "{r}"); + s.execute("document.open", &json!({"name": "t.ai", "dataBase64": r["dataBase64"]})).unwrap(); + let d = &s.doc().unwrap().doc; + let after = [colour_at(d, 15.0, 100.0), colour_at(d, 15.0, 4400.0)]; + for (a, b) in before.iter().zip(&after) { + assert!(a.iter().zip(b).all(|(a, b)| a.abs_diff(*b) <= 3), "{before:?} {after:?}"); + } +} + /// A `size`×`size` PNG of a black disk of radius `r` pixels with an anti-aliased edge, clear /// around it, 72 ppi. fn disk(size: u32, r: f64) -> Vec {