Place: refuse a scale that makes the placed image exceed the render pixel limit (#11229)

* Place: refuse a scale that makes the placed image exceed the render pixel limit

* address review: bound the rounded-up resampled size when placing at a scale

* address review: the oversized-scale error names the placing command
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-11 16:23:05 -05:00
1 parent f6741eff4d
commit 5a935b2830
2 files changed
+28

No files matched your search

@@ -171,6 +171,19 @@ impl PlaceSteps {
Some(c) if c.len() == 2 => (c[0].as_f64().unwrap_or(cw / 2.0), c[1].as_f64().unwrap_or(ch / 2.0)),
_ => (cw / 2.0, ch / 2.0),
};
// `scale` is the caller's: bound the resampled size before allocating it (a frame's image
// renders through its smart object, which has its own budget). Resampling rounds the far
// output edges outward; an unresampled placement keeps its size.
if self.input.frame.is_none() {
let (ow, oh) = if (scale - 1.0).abs() > 1e-9 { ((w * scale).ceil(), (h * scale).ceil()) } else { (w, h) };
let scaled = ow * oh;
if scaled.is_nan() || scaled > photocraft_compose::MAX_RENDER_PIXELS as f64 {
return Err(EngineError::BadParams {
cmd: self.input.command().into(),
msg: format!("scaling {w}×{h} by {scale} makes an image too large to place"),
});
}
}
let (dx, dy) = ((center.0 - w * scale / 2.0).round(), (center.1 - h * scale / 2.0).round());
self.plan = Plan { w, h, scale, dx, dy };
// A frame's image renders through its smart object, which refuses a placement past the
+15
View File
@@ -2004,6 +2004,21 @@ fn place_embedded_centres_fits_and_embeds() {
}
}
/// A caller-chosen placement scale cannot ask for an image beyond the render limit.
#[test]
fn placing_refuses_a_scale_that_would_exceed_the_pixel_limit() {
let dir = tmp("place-huge-scale");
let small = png(&dir, "small.png", 200, 100, "#ff0000");
let mut s = session(100, 100, 8);
for scale in [1e9, 1e300, 1e308, 23170.25] {
assert!(matches!(s.execute("file.placeEmbedded", json!({"path": small, "scale": scale})), Err(EngineError::BadParams { .. })), "{scale}");
}
assert!(s.execute("file.placeEmbedded", json!({"path": small, "scale": 50})).is_ok());
// The error names the command that asked.
let linked = s.execute("file.placeLinked", json!({"path": small, "scale": 1e9}));
assert!(matches!(linked, Err(EngineError::BadParams { ref cmd, .. }) if cmd == "file.placeLinked"), "{linked:?}");
}
/// Preferences ▸ General ▸ Always Create Smart Objects When Placing: unchecked, an embedded place
/// lands as a plain pixel layer; a linked place is always a smart object and `"smartObject"` wins.
#[test]