From df16d21e844af0ff1280e1dc9a82fa2deb9d9919 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 16 Oct 2026 10:02:12 -0700 Subject: [PATCH] Images: the too-large note counts the image's pixels as RGBA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit File › Open, File › Place and the Links panel note an image that needs more than the decode allowance (2 GiB, 512 MiB in the web build). The note counted the bytes the decoder gives, one a pixel for an 8-bit or 1-bit gray image, so a 1-bit gray PNG of 23,171 × 23,170 pixels (537 MB as gray, 2,147,488,280 bytes as RGBA) got no note, although sampling, recoloring and Load Texture refuse it for its RGBA size. The note now counts width × height × 4, or the decoder's bytes when they are more, against the allowance, with the size computed in 64 bits. Such an image gets the note an oversized RGBA image gets. Images within the allowance get no note, as before. --- crates/engine/src/cmd/fileio/load.rs | 13 +++++++----- crates/engine/src/tests_linkspanel.rs | 30 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) 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 }