diff --git a/core/dr-pano/src/fill.rs b/core/dr-pano/src/fill.rs index bd82468..d7aacc1 100644 --- a/core/dr-pano/src/fill.rs +++ b/core/dr-pano/src/fill.rs @@ -303,7 +303,7 @@ fn fill_once( } // The padded canvas with mirrored context, and the hole within it. - let ctx = MirroredContext::build(rgb, width, height, known, mirror_depth); + let ctx = MirroredContext::build(rgb, width, height, known, mirror_depth, t); let (pw, ph) = (ctx.width, ctx.height); // Tiles that touch the hole, on a grid that reaches both far edges. @@ -499,16 +499,36 @@ struct MirroredContext { } impl MirroredContext { - fn build(rgb: &[f32], width: usize, height: usize, known: &[bool], depth: usize) -> Self { + fn build( + rgb: &[f32], + width: usize, + height: usize, + known: &[bool], + depth: usize, + tile: usize, + ) -> Self { if depth == 0 { // Open: the picture as it is, the hole as it is. What the hole - // holds does not matter — the model masks it out. + // holds does not matter — the model masks it out. A picture + // smaller than a tile (the merge page's preview) sits at the + // origin of a tile-sized canvas whose rest is hole: still the + // void as it is, and the only way a tile fits at all. + let (pw, ph) = (width.max(tile), height.max(tile)); + let mut canvas = vec![0.0f32; pw * ph * 3]; + let mut hole = vec![true; pw * ph]; + for y in 0..height { + canvas[y * pw * 3..(y * pw + width) * 3] + .copy_from_slice(&rgb[y * width * 3..(y + 1) * width * 3]); + for x in 0..width { + hole[y * pw + x] = !known[y * width + x]; + } + } return MirroredContext { - width, - height, + width: pw, + height: ph, ring: 0, - rgb: rgb.to_vec(), - hole: known.iter().map(|&k| !k).collect(), + rgb: canvas, + hole, }; } let fold = |d: usize| fold(d, depth); @@ -715,6 +735,30 @@ mod tests { } } + #[test] + fn a_picture_smaller_than_the_tile_is_still_filled_when_the_void_is_open() { + // The merge page's preview is 1600 wide and a few hundred tall — + // shorter than a 512 tile. With no ring the canvas is padded to a + // tile, the padding hole, and the border is still filled. + let (mut rgb, known) = picture(300, 40, 8); + let mut model = Flat { + tile: 64, + seen: Vec::new(), + }; + let tiles = fill_border(&mut rgb, 300, 40, &known, &mut model, test_params(0), &mut |_, _| {}) + .unwrap(); + assert!(tiles > 0, "no tile fitted a picture shorter than the tile"); + for i in 0..300 * 40 { + if !known[i] { + assert!((rgb[i * 3] - 0.5).abs() < 1e-4, "pixel {i}"); + } + } + // And the model saw the padding as hole, never as black content. + for (_, k) in &model.seen { + assert_eq!(k.len(), 64 * 64); + } + } + #[test] fn the_fine_passes_run_in_bands_after_the_coarse_one() { // A 150-tall hole above and below a picture: the coarse pass sees @@ -824,7 +868,7 @@ mod tests { #[test] fn the_context_mirrors_the_top_rows_upward() { let (rgb, known) = picture(40, 30, 5); - let ctx = MirroredContext::build(&rgb, 40, 30, &known, 48); + let ctx = MirroredContext::build(&rgb, 40, 30, &known, 48, 64); let x = RING + 10; let first = RING + 5; for k in 1..=4 { @@ -846,7 +890,7 @@ mod tests { for x in 0..40 { rgb[(ridge * 40 + x) * 3..(ridge * 40 + x) * 3 + 3].copy_from_slice(&[0.9, 0.1, 0.1]); } - let ctx = MirroredContext::build(&rgb, 40, 400, &known, 48); + let ctx = MirroredContext::build(&rgb, 40, 400, &known, 48, 64); let x = RING + 10; for y in 0..RING + 5 { let p = (y * ctx.width + x) * 3;