Merge pull request #2647 from dotCooCoo/fix/too-large-note-counts-rgba

Images: the too-large note counts the image's pixels as RGBA
This commit is contained in:
BFlat
2026-10-17 17:11:14 +07:00
committed by GitHub
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
}