[READ-ONLY] Mirror of https://github.com/mrgnw/spatial-maker.
0

Configure Feed

Select the types of activity you want to include in your feed.

v0.5.2: rayon parallelism for depth filter and stereo fill

Parallelize bilateral filter, gaussian blur, and disocclusion fill using
rayon. Each operates on independent rows. Expected ~4-8x speedup on
Apple Silicon for depth processing.

Also: housekeeping — move stale todo to done, gitignore dist/ and
tarballs, remove old v0.3.x release archives from repo.

+331 -281
+4
.gitignore
··· 34 34 temp*.png 35 35 36 36 37 + # Release artifacts 38 + dist/ 39 + *.tar.gz 40 + 37 41 # Added by cargo 38 42 39 43 /target
+1
Cargo.lock
··· 3025 3025 "ndarray 0.16.1", 3026 3026 "ort", 3027 3027 "ratatui", 3028 + "rayon", 3028 3029 "reqwest", 3029 3030 "serde", 3030 3031 "serde_json",
+2 -1
Cargo.toml
··· 1 1 [package] 2 2 name = "spatial-maker" 3 - version = "0.5.1" 3 + version = "0.5.2" 4 4 edition = "2021" 5 5 license = "MIT" 6 6 description = "Convert 2D images and videos to stereoscopic 3D spatial content for Apple Vision Pro using AI depth estimation" ··· 53 53 [dependencies] 54 54 image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "bmp", "tiff", "webp"] } 55 55 ndarray = "0.16" 56 + rayon = "1" 56 57 thiserror = "2" 57 58 serde = { version = "1.0", features = ["derive"] } 58 59 serde_json = "1.0"
spatial-maker-v0.3.0-aarch64-apple-darwin.tar.gz

This is a binary file and will not be displayed.

spatial-maker-v0.3.1-aarch64-apple-darwin.tar.gz

This is a binary file and will not be displayed.

spatial-maker-v0.3.2-aarch64-apple-darwin.tar.gz

This is a binary file and will not be displayed.

+190 -168
src/depth_filter.rs
··· 1 1 use crate::NormalizeMode; 2 2 use ndarray::Array2; 3 + use rayon::prelude::*; 3 4 4 5 pub struct DepthProcessor { 5 - prev_depth: Option<Array2<f32>>, 6 - ema_min: f32, 7 - ema_max: f32, 8 - global_min: f32, 9 - global_max: f32, 10 - temporal_alpha: f32, 11 - bilateral_sigma_space: f32, 12 - bilateral_sigma_color: f32, 13 - depth_blur_sigma: f32, 14 - normalize_mode: NormalizeMode, 15 - frame_index: u32, 6 + prev_depth: Option<Array2<f32>>, 7 + ema_min: f32, 8 + ema_max: f32, 9 + global_min: f32, 10 + global_max: f32, 11 + temporal_alpha: f32, 12 + bilateral_sigma_space: f32, 13 + bilateral_sigma_color: f32, 14 + depth_blur_sigma: f32, 15 + normalize_mode: NormalizeMode, 16 + frame_index: u32, 16 17 } 17 18 18 19 impl DepthProcessor { 19 - pub fn new( 20 - temporal_alpha: f32, 21 - bilateral_sigma_space: f32, 22 - bilateral_sigma_color: f32, 23 - depth_blur_sigma: f32, 24 - normalize_mode: NormalizeMode, 25 - ) -> Self { 26 - Self { 27 - prev_depth: None, 28 - ema_min: 0.0, 29 - ema_max: 0.0, 30 - global_min: f32::INFINITY, 31 - global_max: f32::NEG_INFINITY, 32 - temporal_alpha, 33 - bilateral_sigma_space, 34 - bilateral_sigma_color, 35 - depth_blur_sigma, 36 - normalize_mode, 37 - frame_index: 0, 38 - } 39 - } 20 + pub fn new( 21 + temporal_alpha: f32, 22 + bilateral_sigma_space: f32, 23 + bilateral_sigma_color: f32, 24 + depth_blur_sigma: f32, 25 + normalize_mode: NormalizeMode, 26 + ) -> Self { 27 + Self { 28 + prev_depth: None, 29 + ema_min: 0.0, 30 + ema_max: 0.0, 31 + global_min: f32::INFINITY, 32 + global_max: f32::NEG_INFINITY, 33 + temporal_alpha, 34 + bilateral_sigma_space, 35 + bilateral_sigma_color, 36 + depth_blur_sigma, 37 + normalize_mode, 38 + frame_index: 0, 39 + } 40 + } 40 41 41 - pub fn set_global_range(&mut self, min: f32, max: f32) { 42 - self.global_min = min; 43 - self.global_max = max; 44 - } 42 + pub fn set_global_range(&mut self, min: f32, max: f32) { 43 + self.global_min = min; 44 + self.global_max = max; 45 + } 45 46 46 - pub fn update_global_range(&mut self, raw_depth: &Array2<f32>) { 47 - let min = raw_depth.iter().copied().fold(f32::INFINITY, f32::min); 48 - let max = raw_depth.iter().copied().fold(f32::NEG_INFINITY, f32::max); 49 - self.global_min = self.global_min.min(min); 50 - self.global_max = self.global_max.max(max); 51 - } 47 + pub fn update_global_range(&mut self, raw_depth: &Array2<f32>) { 48 + let min = raw_depth.iter().copied().fold(f32::INFINITY, f32::min); 49 + let max = raw_depth.iter().copied().fold(f32::NEG_INFINITY, f32::max); 50 + self.global_min = self.global_min.min(min); 51 + self.global_max = self.global_max.max(max); 52 + } 52 53 53 - pub fn process(&mut self, raw_depth: Array2<f32>) -> Array2<f32> { 54 - let mut depth = self.normalize(raw_depth); 54 + pub fn process(&mut self, raw_depth: Array2<f32>) -> Array2<f32> { 55 + let mut depth = self.normalize(raw_depth); 55 56 56 - if self.bilateral_sigma_space > 0.0 { 57 - depth = bilateral_filter(&depth, self.bilateral_sigma_space, self.bilateral_sigma_color); 58 - } 57 + if self.bilateral_sigma_space > 0.0 { 58 + depth = bilateral_filter( 59 + &depth, 60 + self.bilateral_sigma_space, 61 + self.bilateral_sigma_color, 62 + ); 63 + } 59 64 60 - if self.depth_blur_sigma > 0.0 { 61 - depth = gaussian_blur(&depth, self.depth_blur_sigma); 62 - } 65 + if self.depth_blur_sigma > 0.0 { 66 + depth = gaussian_blur(&depth, self.depth_blur_sigma); 67 + } 63 68 64 - if self.temporal_alpha > 0.0 && self.temporal_alpha < 1.0 { 65 - if let Some(ref prev) = self.prev_depth { 66 - if prev.dim() == depth.dim() { 67 - let alpha = self.temporal_alpha; 68 - depth.zip_mut_with(prev, |curr, &prev_val| { 69 - *curr = alpha * *curr + (1.0 - alpha) * prev_val; 70 - }); 71 - } 72 - } 73 - self.prev_depth = Some(depth.clone()); 74 - } 69 + if self.temporal_alpha > 0.0 && self.temporal_alpha < 1.0 { 70 + if let Some(ref prev) = self.prev_depth { 71 + if prev.dim() == depth.dim() { 72 + let alpha = self.temporal_alpha; 73 + depth.zip_mut_with(prev, |curr, &prev_val| { 74 + *curr = alpha * *curr + (1.0 - alpha) * prev_val; 75 + }); 76 + } 77 + } 78 + self.prev_depth = Some(depth.clone()); 79 + } 75 80 76 - self.frame_index += 1; 77 - depth 78 - } 81 + self.frame_index += 1; 82 + depth 83 + } 79 84 80 - fn normalize(&mut self, raw: Array2<f32>) -> Array2<f32> { 81 - match self.normalize_mode { 82 - NormalizeMode::PerFrame => normalize_minmax(raw), 83 - NormalizeMode::RunningEMA => { 84 - let min = raw.iter().copied().fold(f32::INFINITY, f32::min); 85 - let max = raw.iter().copied().fold(f32::NEG_INFINITY, f32::max); 85 + fn normalize(&mut self, raw: Array2<f32>) -> Array2<f32> { 86 + match self.normalize_mode { 87 + NormalizeMode::PerFrame => normalize_minmax(raw), 88 + NormalizeMode::RunningEMA => { 89 + let min = raw.iter().copied().fold(f32::INFINITY, f32::min); 90 + let max = raw.iter().copied().fold(f32::NEG_INFINITY, f32::max); 86 91 87 - let adapt_rate = 0.05; 88 - if self.frame_index == 0 { 89 - self.ema_min = min; 90 - self.ema_max = max; 91 - } else { 92 - self.ema_min = self.ema_min + adapt_rate * (min - self.ema_min); 93 - self.ema_max = self.ema_max + adapt_rate * (max - self.ema_max); 94 - } 92 + let adapt_rate = 0.05; 93 + if self.frame_index == 0 { 94 + self.ema_min = min; 95 + self.ema_max = max; 96 + } else { 97 + self.ema_min = self.ema_min + adapt_rate * (min - self.ema_min); 98 + self.ema_max = self.ema_max + adapt_rate * (max - self.ema_max); 99 + } 95 100 96 - let range = self.ema_max - self.ema_min; 97 - if range > 1e-6 { 98 - raw.mapv(|v| ((v - self.ema_min) / range).clamp(0.0, 1.0)) 99 - } else { 100 - raw.mapv(|_| 0.5) 101 - } 102 - } 103 - NormalizeMode::Global => { 104 - let range = self.global_max - self.global_min; 105 - if range > 1e-6 { 106 - raw.mapv(|v| ((v - self.global_min) / range).clamp(0.0, 1.0)) 107 - } else { 108 - raw.mapv(|_| 0.5) 109 - } 110 - } 111 - } 112 - } 101 + let range = self.ema_max - self.ema_min; 102 + if range > 1e-6 { 103 + raw.mapv(|v| ((v - self.ema_min) / range).clamp(0.0, 1.0)) 104 + } else { 105 + raw.mapv(|_| 0.5) 106 + } 107 + } 108 + NormalizeMode::Global => { 109 + let range = self.global_max - self.global_min; 110 + if range > 1e-6 { 111 + raw.mapv(|v| ((v - self.global_min) / range).clamp(0.0, 1.0)) 112 + } else { 113 + raw.mapv(|_| 0.5) 114 + } 115 + } 116 + } 117 + } 113 118 } 114 119 115 120 fn normalize_minmax(mut depth: Array2<f32>) -> Array2<f32> { 116 - let min = depth.iter().copied().fold(f32::INFINITY, f32::min); 117 - let max = depth.iter().copied().fold(f32::NEG_INFINITY, f32::max); 118 - let range = max - min; 119 - if range > 1e-6 { 120 - depth.mapv_inplace(|v| (v - min) / range); 121 - } 122 - depth 121 + let min = depth.iter().copied().fold(f32::INFINITY, f32::min); 122 + let max = depth.iter().copied().fold(f32::NEG_INFINITY, f32::max); 123 + let range = max - min; 124 + if range > 1e-6 { 125 + depth.mapv_inplace(|v| (v - min) / range); 126 + } 127 + depth 123 128 } 124 129 125 130 pub fn bilateral_filter(depth: &Array2<f32>, sigma_space: f32, sigma_color: f32) -> Array2<f32> { 126 - let (h, w) = depth.dim(); 127 - let mut out = Array2::zeros((h, w)); 128 - let radius = (sigma_space * 2.0).ceil() as i32; 129 - let space_coeff = -0.5 / (sigma_space * sigma_space); 130 - let color_coeff = -0.5 / (sigma_color * sigma_color); 131 + let (h, w) = depth.dim(); 132 + let radius = (sigma_space * 2.0).ceil() as i32; 133 + let space_coeff = -0.5 / (sigma_space * sigma_space); 134 + let color_coeff = -0.5 / (sigma_color * sigma_color); 131 135 132 - for y in 0..h { 133 - for x in 0..w { 134 - let center = depth[[y, x]]; 135 - let mut sum = 0.0f32; 136 - let mut weight_sum = 0.0f32; 136 + let flat: Vec<f32> = (0..h) 137 + .into_par_iter() 138 + .flat_map(|y| { 139 + let mut row = vec![0.0f32; w]; 140 + for x in 0..w { 141 + let center = depth[[y, x]]; 142 + let mut sum = 0.0f32; 143 + let mut weight_sum = 0.0f32; 137 144 138 - let y0 = (y as i32 - radius).max(0) as usize; 139 - let y1 = (y as i32 + radius).min(h as i32 - 1) as usize; 140 - let x0 = (x as i32 - radius).max(0) as usize; 141 - let x1 = (x as i32 + radius).min(w as i32 - 1) as usize; 145 + let y0 = (y as i32 - radius).max(0) as usize; 146 + let y1 = (y as i32 + radius).min(h as i32 - 1) as usize; 147 + let x0 = (x as i32 - radius).max(0) as usize; 148 + let x1 = (x as i32 + radius).min(w as i32 - 1) as usize; 142 149 143 - for ny in y0..=y1 { 144 - for nx in x0..=x1 { 145 - let dy = ny as f32 - y as f32; 146 - let dx = nx as f32 - x as f32; 147 - let spatial_dist = dx * dx + dy * dy; 148 - let val = depth[[ny, nx]]; 149 - let color_dist = (val - center) * (val - center); 150 + for ny in y0..=y1 { 151 + for nx in x0..=x1 { 152 + let dy = ny as f32 - y as f32; 153 + let dx = nx as f32 - x as f32; 154 + let spatial_dist = dx * dx + dy * dy; 155 + let val = depth[[ny, nx]]; 156 + let color_dist = (val - center) * (val - center); 150 157 151 - let weight = (spatial_dist * space_coeff + color_dist * color_coeff).exp(); 152 - sum += val * weight; 153 - weight_sum += weight; 154 - } 155 - } 158 + let weight = (spatial_dist * space_coeff + color_dist * color_coeff).exp(); 159 + sum += val * weight; 160 + weight_sum += weight; 161 + } 162 + } 156 163 157 - out[[y, x]] = if weight_sum > 0.0 { sum / weight_sum } else { center }; 158 - } 159 - } 164 + row[x] = if weight_sum > 0.0 { 165 + sum / weight_sum 166 + } else { 167 + center 168 + }; 169 + } 170 + row 171 + }) 172 + .collect(); 160 173 161 - out 174 + Array2::from_shape_vec((h, w), flat).unwrap() 162 175 } 163 176 164 177 pub fn gaussian_blur(depth: &Array2<f32>, sigma: f32) -> Array2<f32> { 165 - let radius = (sigma * 3.0).ceil() as i32; 166 - let kernel_size = (2 * radius + 1) as usize; 167 - let mut kernel = vec![0.0f32; kernel_size]; 168 - let coeff = -0.5 / (sigma * sigma); 178 + let radius = (sigma * 3.0).ceil() as i32; 179 + let kernel_size = (2 * radius + 1) as usize; 180 + let mut kernel = vec![0.0f32; kernel_size]; 181 + let coeff = -0.5 / (sigma * sigma); 169 182 170 - for i in 0..kernel_size { 171 - let d = i as f32 - radius as f32; 172 - kernel[i] = (d * d * coeff).exp(); 173 - } 174 - let ksum: f32 = kernel.iter().sum(); 175 - for v in &mut kernel { 176 - *v /= ksum; 177 - } 183 + for i in 0..kernel_size { 184 + let d = i as f32 - radius as f32; 185 + kernel[i] = (d * d * coeff).exp(); 186 + } 187 + let ksum: f32 = kernel.iter().sum(); 188 + for v in &mut kernel { 189 + *v /= ksum; 190 + } 178 191 179 - let (h, w) = depth.dim(); 192 + let (h, w) = depth.dim(); 180 193 181 - let mut temp = Array2::zeros((h, w)); 182 - for y in 0..h { 183 - for x in 0..w { 184 - let mut sum = 0.0f32; 185 - for i in 0..kernel_size { 186 - let nx = (x as i32 + i as i32 - radius).clamp(0, w as i32 - 1) as usize; 187 - sum += depth[[y, nx]] * kernel[i]; 188 - } 189 - temp[[y, x]] = sum; 190 - } 191 - } 194 + let temp_flat: Vec<f32> = (0..h) 195 + .into_par_iter() 196 + .flat_map(|y| { 197 + let mut row = vec![0.0f32; w]; 198 + for x in 0..w { 199 + let mut sum = 0.0f32; 200 + for i in 0..kernel_size { 201 + let nx = (x as i32 + i as i32 - radius).clamp(0, w as i32 - 1) as usize; 202 + sum += depth[[y, nx]] * kernel[i]; 203 + } 204 + row[x] = sum; 205 + } 206 + row 207 + }) 208 + .collect(); 209 + let temp = Array2::from_shape_vec((h, w), temp_flat).unwrap(); 192 210 193 - let mut out = Array2::zeros((h, w)); 194 - for y in 0..h { 195 - for x in 0..w { 196 - let mut sum = 0.0f32; 197 - for i in 0..kernel_size { 198 - let ny = (y as i32 + i as i32 - radius).clamp(0, h as i32 - 1) as usize; 199 - sum += temp[[ny, x]] * kernel[i]; 200 - } 201 - out[[y, x]] = sum; 202 - } 203 - } 211 + let out_flat: Vec<f32> = (0..h) 212 + .into_par_iter() 213 + .flat_map(|y| { 214 + let mut row = vec![0.0f32; w]; 215 + for x in 0..w { 216 + let mut sum = 0.0f32; 217 + for i in 0..kernel_size { 218 + let ny = (y as i32 + i as i32 - radius).clamp(0, h as i32 - 1) as usize; 219 + sum += temp[[ny, x]] * kernel[i]; 220 + } 221 + row[x] = sum; 222 + } 223 + row 224 + }) 225 + .collect(); 204 226 205 - out 227 + Array2::from_shape_vec((h, w), out_flat).unwrap() 206 228 }
+133 -111
src/stereo.rs
··· 1 1 use crate::error::SpatialResult; 2 2 use image::{DynamicImage, ImageBuffer, Rgb}; 3 3 use ndarray::Array2; 4 + use rayon::prelude::*; 5 + use std::sync::atomic::{AtomicUsize, Ordering}; 4 6 5 7 pub fn generate_stereo_pair( 6 - image: &DynamicImage, 7 - depth: &Array2<f32>, 8 - max_disparity: u32, 8 + image: &DynamicImage, 9 + depth: &Array2<f32>, 10 + max_disparity: u32, 9 11 ) -> SpatialResult<(DynamicImage, DynamicImage)> { 10 - generate_stereo_pair_with_progress(image, depth, max_disparity, None::<fn(f64)>) 12 + generate_stereo_pair_with_progress(image, depth, max_disparity, None::<fn(f64)>) 11 13 } 12 14 13 15 pub fn generate_stereo_pair_with_progress<F>( 14 - image: &DynamicImage, 15 - depth: &Array2<f32>, 16 - max_disparity: u32, 17 - mut progress_callback: Option<F>, 16 + image: &DynamicImage, 17 + depth: &Array2<f32>, 18 + max_disparity: u32, 19 + mut progress_callback: Option<F>, 18 20 ) -> SpatialResult<(DynamicImage, DynamicImage)> 19 21 where 20 - F: FnMut(f64), 22 + F: FnMut(f64), 21 23 { 22 - let img_rgb = image.to_rgb8(); 23 - let width = img_rgb.width() as usize; 24 - let height = img_rgb.height() as usize; 24 + let img_rgb = image.to_rgb8(); 25 + let width = img_rgb.width() as usize; 26 + let height = img_rgb.height() as usize; 25 27 26 - let mut right_rgb: ImageBuffer<Rgb<u8>, Vec<u8>> = ImageBuffer::new(width as u32, height as u32); 27 - let mut depth_buffer = vec![f32::NEG_INFINITY; width * height]; 28 - let mut filled = vec![false; width * height]; 28 + let mut right_rgb: ImageBuffer<Rgb<u8>, Vec<u8>> = 29 + ImageBuffer::new(width as u32, height as u32); 30 + let mut depth_buffer = vec![f32::NEG_INFINITY; width * height]; 31 + let mut filled = vec![false; width * height]; 29 32 30 - for y in 0..height { 31 - for x in 0..width { 32 - let depth_val = get_depth_at(depth, x, y, width, height); 33 - let disparity = (depth_val * max_disparity as f32).round() as i32; 34 - let x_right = x as i32 - disparity; 33 + for y in 0..height { 34 + for x in 0..width { 35 + let depth_val = get_depth_at(depth, x, y, width, height); 36 + let disparity = (depth_val * max_disparity as f32).round() as i32; 37 + let x_right = x as i32 - disparity; 35 38 36 - if x_right >= 0 && x_right < width as i32 { 37 - let idx = y * width + x_right as usize; 38 - if depth_val > depth_buffer[idx] { 39 - depth_buffer[idx] = depth_val; 40 - filled[idx] = true; 41 - if let Some(pixel) = img_rgb.get_pixel_checked(x as u32, y as u32) { 42 - right_rgb.put_pixel(x_right as u32, y as u32, *pixel); 43 - } 44 - } 45 - } 46 - } 47 - 48 - if let Some(ref mut cb) = progress_callback { 49 - let warp_progress = (y as f64 / height as f64) * 50.0; 50 - cb(warp_progress); 51 - } 52 - } 39 + if x_right >= 0 && x_right < width as i32 { 40 + let idx = y * width + x_right as usize; 41 + if depth_val > depth_buffer[idx] { 42 + depth_buffer[idx] = depth_val; 43 + filled[idx] = true; 44 + if let Some(pixel) = img_rgb.get_pixel_checked(x as u32, y as u32) { 45 + right_rgb.put_pixel(x_right as u32, y as u32, *pixel); 46 + } 47 + } 48 + } 49 + } 50 + 51 + if let Some(ref mut cb) = progress_callback { 52 + let warp_progress = (y as f64 / height as f64) * 50.0; 53 + cb(warp_progress); 54 + } 55 + } 53 56 54 - if let Some(ref mut cb) = progress_callback { 55 - fill_disocclusions_with_progress(&mut right_rgb, &filled, width, height, Some(cb)); 56 - } else { 57 - fill_disocclusions(&mut right_rgb, &filled, width, height); 58 - } 57 + if let Some(ref mut cb) = progress_callback { 58 + fill_disocclusions_with_progress(&mut right_rgb, &filled, width, height, Some(cb)); 59 + } else { 60 + fill_disocclusions(&mut right_rgb, &filled, width, height); 61 + } 59 62 60 - let left_image = image.clone(); 61 - let right_image = DynamicImage::ImageRgb8(right_rgb); 63 + let left_image = image.clone(); 64 + let right_image = DynamicImage::ImageRgb8(right_rgb); 62 65 63 - Ok((left_image, right_image)) 66 + Ok((left_image, right_image)) 64 67 } 65 68 66 69 fn get_depth_at( 67 - depth: &Array2<f32>, 68 - x: usize, 69 - y: usize, 70 - img_width: usize, 71 - img_height: usize, 70 + depth: &Array2<f32>, 71 + x: usize, 72 + y: usize, 73 + img_width: usize, 74 + img_height: usize, 72 75 ) -> f32 { 73 - let (depth_height, depth_width) = depth.dim(); 76 + let (depth_height, depth_width) = depth.dim(); 74 77 75 - if depth_height == img_height && depth_width == img_width { 76 - depth[[y, x]] 77 - } else { 78 - let scaled_x = (x as f32 * depth_width as f32 / img_width as f32) 79 - .min(depth_width as f32 - 1.0) as usize; 80 - let scaled_y = (y as f32 * depth_height as f32 / img_height as f32) 81 - .min(depth_height as f32 - 1.0) as usize; 78 + if depth_height == img_height && depth_width == img_width { 79 + depth[[y, x]] 80 + } else { 81 + let scaled_x = (x as f32 * depth_width as f32 / img_width as f32) 82 + .min(depth_width as f32 - 1.0) as usize; 83 + let scaled_y = (y as f32 * depth_height as f32 / img_height as f32) 84 + .min(depth_height as f32 - 1.0) as usize; 82 85 83 - if scaled_y < depth_height && scaled_x < depth_width { 84 - depth[[scaled_y, scaled_x]] 85 - } else { 86 - 0.5 87 - } 88 - } 86 + if scaled_y < depth_height && scaled_x < depth_width { 87 + depth[[scaled_y, scaled_x]] 88 + } else { 89 + 0.5 90 + } 91 + } 89 92 } 90 93 91 94 fn fill_disocclusions( 92 - image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, 93 - filled: &[bool], 94 - width: usize, 95 - height: usize, 95 + image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, 96 + filled: &[bool], 97 + width: usize, 98 + height: usize, 96 99 ) { 97 - fill_disocclusions_with_progress(image, filled, width, height, None::<fn(f64)>); 100 + fill_disocclusions_with_progress(image, filled, width, height, None::<fn(f64)>); 98 101 } 99 102 100 103 fn fill_disocclusions_with_progress<F>( 101 - image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, 102 - filled: &[bool], 103 - width: usize, 104 - height: usize, 105 - mut progress_callback: Option<F>, 104 + image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, 105 + filled: &[bool], 106 + width: usize, 107 + height: usize, 108 + mut progress_callback: Option<F>, 106 109 ) where 107 - F: FnMut(f64), 110 + F: FnMut(f64), 108 111 { 109 - let original = image.clone(); 112 + let original = image.clone(); 113 + let original_raw = original.as_raw(); 114 + let bytes_per_row = width * 3; 110 115 111 - for y in 0..height { 112 - for x in 0..width { 113 - if filled[y * width + x] { 114 - continue; 115 - } 116 - // scan left to find nearest filled pixel on same row 117 - let mut left_pixel = None; 118 - for lx in (0..x).rev() { 119 - if filled[y * width + lx] { 120 - left_pixel = Some(*original.get_pixel(lx as u32, y as u32)); 121 - break; 122 - } 123 - } 124 - // scan right 125 - let mut right_pixel = None; 126 - for rx in (x + 1)..width { 127 - if filled[y * width + rx] { 128 - right_pixel = Some(*original.get_pixel(rx as u32, y as u32)); 129 - break; 130 - } 131 - } 116 + let counter = AtomicUsize::new(0); 132 117 133 - let fill = match (left_pixel, right_pixel) { 134 - (Some(l), Some(_)) => l, // prefer left (background side in DIBR) 135 - (Some(l), None) => l, 136 - (None, Some(r)) => r, 137 - (None, None) => continue, 138 - }; 139 - image.put_pixel(x as u32, y as u32, fill); 140 - } 141 - 142 - if let Some(cb) = progress_callback.as_mut() { 143 - let fill_progress = 50.0 + (y as f64 / height as f64) * 50.0; 144 - cb(fill_progress); 145 - } 146 - } 118 + let output_raw = image.as_mut(); 119 + output_raw 120 + .par_chunks_mut(bytes_per_row) 121 + .enumerate() 122 + .for_each(|(y, row_pixels)| { 123 + let row_filled = &filled[y * width..(y + 1) * width]; 124 + let orig_row = &original_raw[y * bytes_per_row..(y + 1) * bytes_per_row]; 125 + 126 + for x in 0..width { 127 + if row_filled[x] { 128 + continue; 129 + } 130 + 131 + let mut left_pixel = None; 132 + for lx in (0..x).rev() { 133 + if row_filled[lx] { 134 + let off = lx * 3; 135 + left_pixel = Some([orig_row[off], orig_row[off + 1], orig_row[off + 2]]); 136 + break; 137 + } 138 + } 139 + 140 + let mut right_pixel = None; 141 + for rx in (x + 1)..width { 142 + if row_filled[rx] { 143 + let off = rx * 3; 144 + right_pixel = Some([orig_row[off], orig_row[off + 1], orig_row[off + 2]]); 145 + break; 146 + } 147 + } 148 + 149 + let fill = match (left_pixel, right_pixel) { 150 + (Some(l), Some(_)) => l, 151 + (Some(l), None) => l, 152 + (None, Some(r)) => r, 153 + (None, None) => continue, 154 + }; 155 + let off = x * 3; 156 + row_pixels[off] = fill[0]; 157 + row_pixels[off + 1] = fill[1]; 158 + row_pixels[off + 2] = fill[2]; 159 + } 160 + 161 + counter.fetch_add(1, Ordering::Relaxed); 162 + }); 163 + 164 + if let Some(ref mut cb) = progress_callback { 165 + let done = counter.load(Ordering::Relaxed); 166 + let fill_progress = 50.0 + (done as f64 / height as f64) * 50.0; 167 + cb(fill_progress); 168 + } 147 169 }
+1 -1
todos/_top.md
··· 1 - 1. spatial-settings-overhaul.md 1 +
todos/spatial-settings-overhaul.md todos/done/0303-spatial-settings-overhaul.md