diff --git a/crates/engine/src/cmd/fileio/load.rs b/crates/engine/src/cmd/fileio/load.rs index 147337fc8..8abc7b585 100644 --- a/crates/engine/src/cmd/fileio/load.rs +++ b/crates/engine/src/cmd/fileio/load.rs @@ -448,10 +448,10 @@ pub(super) fn new_from_template(s: &mut Session, p: &Value) -> Result { /// that default: wasm32 has 4 GB to address and aborts when an allocation fails. pub(crate) use vectorcraft_doc::MAX_RASTER_ALLOC; -/// What to say of encoded image `bytes` that need more than [`MAX_RASTER_ALLOC`] to decode (its -/// header says so; the pixels aren't read): the renderer, which has the same allowance, draws -/// nothing for them, so Open, Place and the Links panel say it (#1479). `None` for images that -/// decode, and for bytes that aren't an image. +/// What to say of encoded image `bytes` that need more than [`MAX_RASTER_ALLOC`] to decode, +/// counted as RGBA (4 bytes a pixel) or as the decoder gives them when that is more (its header +/// says so; the pixels aren't read): Open, Place and the Links panel say it (#1479). `None` for +/// images within the allowance, and for bytes that aren't an image. pub(crate) fn too_large_note(bytes: &[u8]) -> Option { use image::ImageDecoder as _; if let Some((w, h)) = vectorcraft_doc::cmyk::oversized_tiff(bytes) { @@ -459,7 +459,10 @@ pub(crate) fn too_large_note(bytes: &[u8]) -> Option { } let d = image::ImageReader::new(Cursor::new(bytes)).with_guessed_format().ok()?.into_decoder().ok()?; let (w, h) = d.dimensions(); - (d.total_bytes() > MAX_RASTER_ALLOC).then(|| format!("an image of {w} × {h} pixels is too large to draw")) + // The allowance also counts the pixels as RGBA: an image of fewer than 4 bytes a pixel (gray, + // gray with alpha, RGB) takes more than the decoder counts (#1717). + let rgba_over = u64::from(w) * u64::from(h) > MAX_RASTER_ALLOC / 4; + (rgba_over || d.total_bytes() > MAX_RASTER_ALLOC).then(|| format!("an image of {w} × {h} pixels is too large to draw")) } /// Decode an image's header (and, for formats stored as PNG, its pixels). CMYK TIFFs are kept as diff --git a/crates/engine/src/tests_linkspanel.rs b/crates/engine/src/tests_linkspanel.rs index 2899ce021..99dafda8c 100644 --- a/crates/engine/src/tests_linkspanel.rs +++ b/crates/engine/src/tests_linkspanel.rs @@ -49,6 +49,36 @@ fn an_image_too_large_to_draw_is_reported() { assert_eq!(crate::cmd::fileio::load("wall.png", &png_header(200_000, 200_000)).unwrap().warnings, [NOTE]); assert!(crate::cmd::fileio::load("fine.png", &png_header(16_000, 16_000)).unwrap().warnings.is_empty()); } + +/// A gray image is counted as RGBA, 4 bytes a pixel: 23,171 × 23,170 1-bit pixels decode to 537 MB +/// of gray and are 2,147,488,280 bytes as RGBA, past the 2 GiB allowance; 23,170 × 23,170 are +/// 2,147,395,600 bytes, within it. +#[test] +fn a_gray_image_too_large_as_rgba_is_reported_on_place() { + const NOTE: &str = "an image of 23171 × 23170 pixels is too large to draw"; + let dir = Folder::new("huge-gray-place"); + let (over, within) = (dir.file("over.png"), dir.file("within.png")); + write(&over, &png_header(23_171, 23_170)); + write(&within, &png_header(23_170, 23_170)); + let mut s = session(); + assert_eq!(run(&mut s, "file.place", json!({"path": over, "at": [200, 150], "link": false}))["warnings"], json!([NOTE])); + assert_eq!(run(&mut s, "file.place", json!({"path": within, "at": [20, 20], "link": false}))["warnings"], json!([])); + let l = run(&mut s, "links.list", json!({})); + let notes: Vec<&Value> = l["links"].as_array().unwrap().iter().map(|r| &r["note"]).collect(); + assert_eq!(notes, [&Value::Null, &json!(NOTE)], "top first: the image within the allowance has nothing to say"); +} + +/// File › Open says the same of a gray image past the allowance as RGBA, and nothing of one within it. +#[test] +fn a_gray_image_too_large_as_rgba_is_reported_on_open() { + let dir = Folder::new("huge-gray-open"); + let (over, within) = (dir.file("over.png"), dir.file("within.png")); + write(&over, &png_header(23_171, 23_170)); + write(&within, &png_header(23_170, 23_170)); + let mut s = session(); + assert_eq!(open(&mut s, &over)["warnings"], json!(["an image of 23171 × 23170 pixels is too large to draw"])); + assert_eq!(open(&mut s, &within)["warnings"], json!([])); +} fn close(a: f64, b: f64) -> bool { (a - b).abs() < 1e-6 }