Images: the too-large note counts the image's pixels as RGBA

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.
This commit is contained in:
dotCooCoo
2026-10-17 16:38:42 +07:00
committed by bflatastic
parent 133fd73feb
commit df16d21e84
2 changed files with 38 additions and 5 deletions
+8 -5
View File
@@ -448,10 +448,10 @@ pub(super) fn new_from_template(s: &mut Session, p: &Value) -> Result<Value> {
/// 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<String> {
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<String> {
}
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
+30
View File
@@ -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
}