Repository navigation
A region of interest over an adjustment layer shows the full frame's pixels #2012
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1028,6 +1028,27 @@ impl<'a> Renderer<'a> { | |
| } | ||
| return out; | ||
| } | ||
| // Region of interest over an adjustment layer with effects: the stack reads the whole | ||
| // comp below (a Transform scaling it down, a blur, Auto Levels), not just the region. | ||
| // Render the frame with the region around it and crop: inside the frame that is the | ||
| // whole frame (on any backend), past it the region grown to hold the frame. | ||
| if self.depth == 0 | ||
| && let Some(r) = self.opts.roi | ||
| && !roi_holds_frame(r, comp.width, comp.height) | ||
| && comp.layers.iter().any(adjusts_below) | ||
| { | ||
| let (cw, ch) = (comp.width as f64, comp.height as f64); | ||
| let (x0, y0) = (r[0].min(0.0), r[1].min(0.0)); | ||
| let u = [x0, y0, (r[0] + r[2]).max(cw) - x0, (r[1] + r[3]).max(ch) - y0]; | ||
| let frame = if roi_inside(r, comp.width, comp.height) { | ||
| Renderer { opts: RenderOpts { roi: None, ..self.opts }, ..*self }.comp_frame(comp_id, t) | ||
| } else { | ||
| Renderer { opts: RenderOpts { roi: Some(u), ..self.opts }, ..*self }.comp_frame_cpu(comp_id, t) | ||
| }; | ||
| let px = |v: f64| (v * s).round(); | ||
| let (rw, rh) = ((px(r[2]) as u32).max(1), (px(r[3]) as u32).max(1)); | ||
| return frame.crop((px(r[0]) - px(x0)) as i64, (px(r[1]) - px(y0)) as i64, rw, rh); | ||
| } | ||
| let (w, h) = match self.roi_offset() { | ||
| Some((_, _, rw, rh)) => (rw, rh), | ||
| None => (w, h), | ||
|
|
@@ -2036,13 +2057,29 @@ impl<'a> Renderer<'a> { | |
| let mut matte = Image::new(canvas.width, canvas.height); | ||
| self.place(ctx, layer, &foot, &mut matte, BlendMode::Normal, 1.0); | ||
| self.apply_track_matte(ctx, layer, &mut matte, None); | ||
| let o = self.roi_offset().map(|(x, y, _, _)| [-x, -y]).unwrap_or([0.0; 2]); | ||
| let mut below = Buf { img: canvas.clone(), offset: o, scale: self.opts.scale }; | ||
| let (x, y) = self.out_offset(); | ||
| // A region holding the whole frame (the Extended Viewer): the stack gets the comp frame | ||
| // below and nothing past it, as in a render, so a Transform scaling it down doesn't pull | ||
| // in layers lying off the frame. | ||
| let s = self.opts.scale; | ||
| let frame = self.opts.roi.filter(|r| self.top && roi_holds_frame(*r, ctx.comp.width, ctx.comp.height)); | ||
| let mut below = match frame { | ||
| Some(_) => { | ||
| let (w, h) = (((ctx.comp.width as f64 * s).round() as u32).max(1), ((ctx.comp.height as f64 * s).round() as u32).max(1)); | ||
| Buf { img: canvas.crop(-x as i64, -y as i64, w, h), offset: [0.0; 2], scale: s } | ||
|
Comment on lines
+2065
to
+2069
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Do not use nested-comp bounds to crop the parent canvas. When a collapsed precomp draws into the top-level canvas, 🤖 Prompt for AI Agents |
||
| } | ||
| None => Buf { img: canvas.clone(), offset: [-x, -y], scale: s }, | ||
| }; | ||
| // Effects run in the working space, not the linear blending space. | ||
| if let Some(c) = self.pipe.from_blend() { | ||
| color::convert(&mut below.img, &c); | ||
| } | ||
| let mut adjusted = self.apply_effects(ctx, layer, below, true); | ||
| // Back onto the canvas: the result pixel at canvas pixel (0, 0). | ||
| let (dx, dy) = ((adjusted.offset[0] + x).round(), (adjusted.offset[1] + y).round()); | ||
| if dx != 0.0 || dy != 0.0 || adjusted.img.width != canvas.width || adjusted.img.height != canvas.height { | ||
| adjusted.img = adjusted.img.crop(dx as i64, dy as i64, canvas.width, canvas.height); | ||
| } | ||
|
Comment on lines
+2080
to
+2082
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Keep pasteboard pixels outside the effect input. If an adjustment footprint extends beyond the comp frame, this crop pads the frame-sized effect result with transparent pixels. The later footprint blend then replaces visible pasteboard content with that transparency. For example, a wider adjustment solid over the out-of-frame yellow layer loses the yellow pixels under its footprint, even with a per-pixel effect. Keep the existing canvas pixels outside the frame when aligning the result. 🤖 Prompt for AI Agents |
||
| if let Some(c) = self.pipe.to_blend() { | ||
| color::convert(&mut adjusted.img, &c); | ||
| } | ||
|
|
@@ -2344,6 +2381,16 @@ pub fn roi_inside(r: [f64; 4], w: u32, h: u32) -> bool { | |
| r[0] >= 0.0 && r[1] >= 0.0 && r[0] + r[2] <= w as f64 + 1e-6 && r[1] + r[3] <= h as f64 + 1e-6 | ||
| } | ||
|
|
||
| /// Whether region of interest `r` holds the whole `w` × `h` comp frame. | ||
| fn roi_holds_frame(r: [f64; 4], w: u32, h: u32) -> bool { | ||
| r[0] <= 0.0 && r[1] <= 0.0 && r[0] + r[2] >= w as f64 - 1e-6 && r[1] + r[3] >= h as f64 - 1e-6 | ||
| } | ||
|
|
||
| /// An adjustment layer with an enabled effect: its stack runs on the comp below. | ||
| fn adjusts_below(l: &Layer) -> bool { | ||
| l.switches.adjustment && l.switches.effects && l.effects().is_some_and(|fx| fx.groups().any(|g| g.enabled && matches!(g.kind, GroupKind::Effect { .. }))) | ||
| } | ||
|
|
||
| /// The Extended Viewer's region of interest: the comp frame grown by `margin` × its size on every | ||
| /// side (`margin` = 1 shows a pasteboard one comp wide around the frame), intersected with the | ||
| /// `visible` comp-space rectangle `[x0, y0, x1, y1]` and snapped outward to whole 16 px steps from the frame edges | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Preserve the nonpositive-ROI fallback.
If
r[2]orr[3]is zero, this branch can run beforeroi_offset()rejects the ROI. It returns a one-pixel crop instead of the full frame. Apply the same positive-size check before expanding the ROI.🤖 Prompt for AI Agents