mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-29 20:47:18 -06:00
Merge pull request #1568 from midasmatterhorn/fix/r4-output-size-clamp
project: output_size never asks for sides past the comp limit
This commit is contained in:
@@ -619,7 +619,7 @@ fn set_output_module(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (w, h) = item.settings.output_size(comp);
|
||||
let mut crop_only = om.clone();
|
||||
crop_only.resize.enabled = false;
|
||||
let (cw, ch) = crop_only.frame_size(w, h, item.settings.resolution.clamp(0.01, 4.0));
|
||||
let (cw, ch) = crop_only.frame_size(w, h, item.settings.effective_scale(comp));
|
||||
om.resize.width = ((om.resize.height as f64 * cw as f64 / ch as f64).round() as u32).clamp(1, 30_000);
|
||||
}
|
||||
let post = match str_p(p, "postRenderAction") {
|
||||
|
||||
@@ -240,7 +240,7 @@ impl<'a> Cx<'a> {
|
||||
// compositors differ slightly, so a per-frame choice made renders differ run to run.
|
||||
let backend = if self.project.settings.gpu_acceleration { Backend::Gpu } else { Backend::Cpu };
|
||||
let opts = RenderOpts {
|
||||
scale: s.resolution.clamp(0.01, 4.0),
|
||||
scale: self.comp().map(|c| s.effective_scale(c)).unwrap_or_else(|| s.resolution.clamp(0.01, 4.0)),
|
||||
motion_blur: s.motion_blur_override() != SwitchOverride::OffForAll,
|
||||
guides: s.guide_layers == CurrentOrOff::Current,
|
||||
draft: s.quality == RenderQuality::Draft,
|
||||
@@ -389,7 +389,7 @@ impl<'a> Cx<'a> {
|
||||
return None;
|
||||
}
|
||||
let om = self.job.output;
|
||||
let scale = self.job.settings.resolution.clamp(0.01, 4.0);
|
||||
let scale = self.job.settings.effective_scale(comp);
|
||||
let size = |n: u32| ((n as f64 * scale).round() as u32).max(1);
|
||||
if om.resize.enabled || (size(comp.width), size(comp.height)) != (w, h) || om.crop.edges(w, h, scale) != (0, 0, 0, 0) {
|
||||
return None;
|
||||
@@ -435,7 +435,7 @@ impl<'a> Cx<'a> {
|
||||
/// Output Module ▸ Crop, then Resize.
|
||||
fn crop_resize(&self, img: Image) -> Image {
|
||||
let om = self.job.output;
|
||||
let scale = self.job.settings.resolution.clamp(0.01, 4.0);
|
||||
let scale = self.comp().map(|c| self.job.settings.effective_scale(c)).unwrap_or_else(|| self.job.settings.resolution.clamp(0.01, 4.0));
|
||||
let (t, l, b, r) = om.crop.edges(img.width, img.height, scale);
|
||||
let img = if (t, l, b, r) == (0, 0, 0, 0) { img } else { crop(&img, t, l, b, r) };
|
||||
if !om.resize.enabled {
|
||||
|
||||
@@ -442,9 +442,18 @@ impl RenderSettings {
|
||||
pub fn frame_time(&self, comp: &Comp, i: u64) -> Tick {
|
||||
self.rate(comp).tick_of(self.first_frame(comp) + i as i64)
|
||||
}
|
||||
pub fn output_size(&self, comp: &Comp) -> (u32, u32) {
|
||||
pub fn effective_scale(&self, comp: &Comp) -> f64 {
|
||||
let s = self.resolution.clamp(0.01, 4.0);
|
||||
(((comp.width as f64 * s).round() as u32).max(1), ((comp.height as f64 * s).round() as u32).max(1))
|
||||
let max = f64::from(crate::limits::MAX_SIZE);
|
||||
s.min(max / f64::from(comp.width.max(1))).min(max / f64::from(comp.height.max(1)))
|
||||
}
|
||||
pub fn output_size(&self, comp: &Comp) -> (u32, u32) {
|
||||
let s = self.effective_scale(comp);
|
||||
// The scale is clamped so neither side exceeds the comp limit, keeping the aspect
|
||||
// ratio: a 30 000 px side at 4x renders at 1x instead of cropping to a corner.
|
||||
let w = ((comp.width as f64 * s).round() as u32).max(1);
|
||||
let h = ((comp.height as f64 * s).round() as u32).max(1);
|
||||
(w, h)
|
||||
}
|
||||
pub fn resolution_label(&self) -> &'static str {
|
||||
match self.resolution {
|
||||
@@ -1126,7 +1135,7 @@ impl OutputModule {
|
||||
/// rounding).
|
||||
pub fn output_size(&self, comp: &Comp, settings: &RenderSettings) -> (u32, u32) {
|
||||
let (w, h) = settings.output_size(comp);
|
||||
let (w, h) = self.frame_size(w, h, settings.resolution.clamp(0.01, 4.0));
|
||||
let (w, h) = self.frame_size(w, h, settings.effective_scale(comp));
|
||||
self.format.coded_size(w, h)
|
||||
}
|
||||
/// Every setting as `(label, value)` lines (render logs).
|
||||
@@ -1673,6 +1682,28 @@ mod tests {
|
||||
assert_eq!(m.output_size(&c, &half), (400, 200));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_size_never_exceeds_the_comp_limit() {
|
||||
// The effective scale is clamped so neither side exceeds the limit (exporter
|
||||
// `w * h * 4` would overflow at 120 000 px sides); the pipeline renders at the
|
||||
// same scale, so it never requests a frame larger than this reports.
|
||||
let rs = RenderSettings { resolution: 4.0, ..Default::default() };
|
||||
let m = OutputModule::for_format(OutputFormat::PngSequence);
|
||||
let square = Comp::new(30_000, 30_000, FrameRate::new(30, 1), Tick::from_seconds_f64(1.0));
|
||||
assert_eq!(rs.effective_scale(&square), 1.0);
|
||||
assert_eq!(rs.output_size(&square), (30_000, 30_000));
|
||||
assert_eq!(m.output_size(&square, &rs), (30_000, 30_000));
|
||||
// Wide comps keep their aspect ratio instead of clamping each side on its own.
|
||||
let wide = Comp::new(30_000, 15_000, FrameRate::new(30, 1), Tick::from_seconds_f64(1.0));
|
||||
assert_eq!(rs.effective_scale(&wide), 1.0);
|
||||
assert_eq!(rs.output_size(&wide), (30_000, 15_000));
|
||||
assert_eq!(m.output_size(&wide, &rs), (30_000, 15_000));
|
||||
// A mid-size comp renders at the largest scale that still fits.
|
||||
let mid = Comp::new(20_000, 10_000, FrameRate::new(30, 1), Tick::from_seconds_f64(1.0));
|
||||
assert_eq!(mid.width as f64 * rs.effective_scale(&mid), 30_000.0);
|
||||
assert_eq!(rs.output_size(&mid), (30_000, 15_000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncompressed_quicktime_format() {
|
||||
let f = OutputFormat::from_name("uncompressed").unwrap();
|
||||
|
||||
Reference in New Issue
Block a user