Merge branch 'worktree-agent-a22a049c461818dbe' into integration
# Conflicts: # core/dr-pipeline/tests/mask_sidecar.rs
This commit is contained in:
@@ -189,3 +189,135 @@ fn the_mask_covers_the_same_fraction_at_every_size() {
|
||||
"and that share is the left half: {small:.3}"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Gradient geometry
|
||||
//
|
||||
// The mask is rasterised over the source frame, whose two axes are not the
|
||||
// same length. A gradient measured in raw 0..1 fractions therefore means a
|
||||
// different distance horizontally than vertically — so a circle comes out an
|
||||
// ellipse and an angle is not the angle asked for. Neither shows in the stored
|
||||
// numbers, and both are what a photographer is looking straight at while
|
||||
// dragging a handle.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A flat grey frame at an arbitrary shape, brightened wherever `stack` covers.
|
||||
fn render_gradient(ctx: &GpuContext, stack: &MaskStack, w: u32, h: u32) -> Vec<u8> {
|
||||
let data: Vec<u8> = (0..w * h).flat_map(|_| [128u8, 128, 128, 255]).collect();
|
||||
let source = DemosaicedImage::from_rgba8(ctx, &data, w, h).expect("upload");
|
||||
|
||||
let mut masks = MaskPass::new(ctx).expect("mask pass");
|
||||
let array = masks.render(stack, None, None, w, h).expect("rasterise");
|
||||
|
||||
let shader = compose_full(&ops::chain(), &Framing::new(), ColourSpace::Srgb, stack);
|
||||
let mut adjust = AdjustPass::new(ctx);
|
||||
adjust
|
||||
.render_masked(&source, &shader, w, h, Some(array))
|
||||
.expect("render");
|
||||
adjust.export_pixels().expect("readback").0
|
||||
}
|
||||
|
||||
fn brightened(pixels: &[u8], w: u32, x: u32, y: u32) -> bool {
|
||||
pixels[((y * w + x) * 4) as usize] > 160
|
||||
}
|
||||
|
||||
fn gradient(source: MaskSource) -> MaskStack {
|
||||
let mut stack = MaskStack::new();
|
||||
let mut layer = MaskLayer::new("g1", source);
|
||||
layer.set_param("exposure", ParamId("exposure"), 2.0);
|
||||
stack.push(layer);
|
||||
stack
|
||||
}
|
||||
|
||||
/// A radial with equal radii must be round on the screen, not on the numbers.
|
||||
#[test]
|
||||
fn a_radial_with_equal_radii_is_a_circle_on_a_wide_frame() {
|
||||
let Some(ctx) = ctx() else {
|
||||
eprintln!("no adapter; skipping");
|
||||
return;
|
||||
};
|
||||
// 3:2. On a square frame this test cannot fail, which is why it is not one.
|
||||
const W: u32 = 96;
|
||||
const H: u32 = 64;
|
||||
|
||||
// 0.3 of the frame's height. In pixels that is 19 either way — the x axis
|
||||
// spans 0..1.5 in the same units, so the fraction is smaller and the
|
||||
// distance is the same.
|
||||
let pixels = render_gradient(
|
||||
&ctx,
|
||||
&gradient(MaskSource::Radial {
|
||||
centre: (0.5, 0.5),
|
||||
radii: (0.3, 0.3),
|
||||
angle: 0.0,
|
||||
// Hard, so "covered" is a question with an answer rather than a
|
||||
// ramp to pick a threshold out of.
|
||||
feather: 0.0,
|
||||
}),
|
||||
W,
|
||||
H,
|
||||
);
|
||||
|
||||
let (cx, cy) = (W / 2, H / 2);
|
||||
assert!(brightened(&pixels, W, cx, cy), "the centre must be covered");
|
||||
|
||||
for (dx, dy, what) in [(15u32, 0u32, "right"), (0, 15, "down")] {
|
||||
assert!(
|
||||
brightened(&pixels, W, cx + dx, cy + dy),
|
||||
"15px {what} of centre is inside a 19px radius"
|
||||
);
|
||||
}
|
||||
for (dx, dy, what) in [(24u32, 0u32, "right"), (0, 24, "down")] {
|
||||
assert!(
|
||||
!brightened(&pixels, W, cx + dx, cy + dy),
|
||||
"24px {what} of centre is outside it — before the aspect \
|
||||
correction the horizontal reach was 28px and this passed only \
|
||||
downwards"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// And a ramp at 45° must be at 45° where the photographer sees it.
|
||||
#[test]
|
||||
fn a_diagonal_ramp_runs_at_the_angle_it_was_given() {
|
||||
let Some(ctx) = ctx() else {
|
||||
eprintln!("no adapter; skipping");
|
||||
return;
|
||||
};
|
||||
const W: u32 = 96;
|
||||
const H: u32 = 64;
|
||||
|
||||
let pixels = render_gradient(
|
||||
&ctx,
|
||||
&gradient(MaskSource::Linear {
|
||||
centre: (0.5, 0.5),
|
||||
angle: std::f32::consts::FRAC_PI_4,
|
||||
width: 0.0,
|
||||
}),
|
||||
W,
|
||||
H,
|
||||
);
|
||||
|
||||
// Coverage increases along (cos 45°, sin 45°), so the half-way line runs
|
||||
// from lower-left to upper-right through the centre: equal steps right and
|
||||
// down stay on the covered side, and the two sides of it disagree.
|
||||
let (cx, cy) = (W / 2, H / 2);
|
||||
assert!(
|
||||
brightened(&pixels, W, cx + 16, cy + 16),
|
||||
"down and to the right of the line is inside"
|
||||
);
|
||||
assert!(
|
||||
!brightened(&pixels, W, cx - 16, cy - 16),
|
||||
"up and to the left of it is outside"
|
||||
);
|
||||
// The diagonal itself, sampled a few pixels either side. Without the
|
||||
// aspect correction the line comes out at 34 degrees and both of these
|
||||
// land on the same side of it.
|
||||
assert!(
|
||||
brightened(&pixels, W, cx + 18, cy - 14),
|
||||
"just below the 45 degree line is inside"
|
||||
);
|
||||
assert!(
|
||||
!brightened(&pixels, W, cx + 14, cy - 18),
|
||||
"just above it is not"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user