fix(render-queue): keep Output Module crop padding within the largest output frame

This commit is contained in:
Dylan M. Taylor
2026-11-03 14:42:34 +00:00
parent 7870b5916a
commit 462b2a51f0
+40 -3
View File
@@ -779,6 +779,24 @@ pub struct Crop {
pub right: i32,
}
/// The largest side of a frame an output module writes (Resize is capped there too).
pub const MAX_OUTPUT_SIDE: i32 = 30_000;
/// Crop edges `a`/`b` of one axis of `side` pixels, each at most the side and padding at most
/// up to [`MAX_OUTPUT_SIDE`] in all.
fn bound_edges(a: i32, b: i32, side: i32) -> (i32, i32) {
let (mut a, mut b) = (a.clamp(-MAX_OUTPUT_SIDE, side), b.clamp(-MAX_OUTPUT_SIDE, side));
let excess = i64::from(side) - i64::from(a) - i64::from(b) - i64::from(MAX_OUTPUT_SIDE);
if excess > 0 {
// Only padding (negative edges) grows the side: take back as much of it as needed.
let excess = i32::try_from(excess).unwrap_or(i32::MAX);
let from_a = excess.min(-a.min(0));
a += from_a;
b += (excess - from_a).min(-b.min(0));
}
(a, b)
}
impl Crop {
/// Edges `(top, left, bottom, right)` in pixels of a `w`×`h` frame rendered at `scale`.
pub fn edges(&self, w: u32, h: u32, scale: f64) -> (i32, i32, i32, i32) {
@@ -801,7 +819,12 @@ impl Crop {
let b = conv((y + rh) * scale, hi).max(t);
return (t, l, hi - b, wi - r);
}
(self.top, self.left, self.bottom, self.right)
// Negative edges pad the frame: keep each padded side within the largest frame an
// output module writes, or a few saved integers ask for a canvas of gigapixels.
let (wi, hi) = (i32::try_from(w).unwrap_or(i32::MAX), i32::try_from(h).unwrap_or(i32::MAX));
let (t, b) = bound_edges(self.top, self.bottom, hi);
let (l, r) = bound_edges(self.left, self.right, wi);
(t, l, b, r)
}
}
@@ -1130,8 +1153,8 @@ impl OutputModule {
// Width rules; height follows the cropped frame's aspect.
rh = ((rw as f64 * ch as f64 / cw as f64).round() as u32).max(1);
}
rw = rw.min(30_000);
rh = rh.min(30_000);
rw = rw.min(MAX_OUTPUT_SIDE as u32);
rh = rh.min(MAX_OUTPUT_SIDE as u32);
(rw, rh)
}
/// The written frame size of an item (Render Settings resolution, Crop, Resize, codec
@@ -1534,6 +1557,20 @@ pub fn sanitize(name: &str) -> String {
#[cfg(test)]
mod tests {
#[test]
fn crop_padding_stays_within_the_largest_output_frame() {
use super::*;
let crop = |top, left, bottom, right| Crop { enabled: true, top, left, bottom, right, ..Default::default() };
let m = |c: Crop| OutputModule { crop: c, ..Default::default() };
// Saved edges padding by billions of pixels: the frame stops at the cap.
assert_eq!(m(crop(i32::MIN, i32::MIN, i32::MIN, i32::MIN)).frame_size(100, 50, 1.0), (30_000, 30_000));
assert_eq!(m(crop(-20_000, 0, -20_000, -1)).frame_size(100, 50, 1.0), (101, 30_000));
// Ordinary crops and pads are unchanged.
assert_eq!(crop(1, -2, 3, 4).edges(100, 50, 1.0), (1, -2, 3, 4));
assert_eq!(m(crop(10, 10, 10, 10)).frame_size(100, 50, 1.0), (80, 30));
assert_eq!(m(crop(i32::MAX, 0, 0, 0)).frame_size(100, 50, 1.0), (100, 1));
}
use super::*;
#[test]