diff --git a/Cargo.lock b/Cargo.lock index 7d05b58..443f261 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,95 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "gimli" version = "0.31.1" @@ -224,6 +313,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "png" version = "0.17.16" @@ -318,6 +413,7 @@ name = "stitcher" version = "0.1.0" dependencies = [ "fast_image_resize", + "futures", "png", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index eca94d1..2b6d10e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,9 @@ name = "stitcher" version = "0.1.0" edition = "2024" + [dependencies] png = "0.17.16" tokio = { version = "1.46.0", features = ["full"] } fast_image_resize = "5.1.4" +futures = "0.3.31" diff --git a/src/main.rs b/src/main.rs index 7a38ec7..b283b02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,75 +1,173 @@ use fast_image_resize as fr; use fr::{PixelType, ResizeAlg, ResizeOptions, Resizer, images::Image}; -use std::io::Cursor; +use futures::future::join_all; +use std::{ + cell::UnsafeCell, + io::Cursor, + io::Write, + ops::{Bound, RangeBounds}, +}; use tokio::fs; +// use tokio::net::TcpListener; #[tokio::main] -async fn main() { - let start = std::time::Instant::now(); - let base_path = "/mnt/c/Users/Formulatrix/Documents/Explorations/WebApplication1/tiles1705/"; - const SCALE: f64 = 0.3; +async fn main() -> Result<(), Box> { + // let listener = TcpListener::bind("127.0.0.1:8080").await?; + // println!("Server listening on port 8080"); + // + // loop { + // let (socket, _) = listener.accept().await?; + // tokio::spawn(async move { + // if let Err(e) = handle_client(socket).await { + // eprintln!("Error: {}", e); + // } + // }); + // } + // } + // + // async fn handle_client(socket: tokio::net::TcpStream) -> Result<(), Box> { + // // let file = std::fs::File::create("raw_image_123.png").unwrap(); + // let offset_x = 0.5; + // let size_x = 0.5; + // let offset_y = 0.5; + // let size_y = 0.5; + // let rect = "A1:AE55"; + // let scale = 0.4; + // + // resize_image( + // &mut socket.into_std().unwrap(), + // rect, + // scale, + // offset_x, + // offset_y, + // size_x, + // size_y, + // ) + // .await; + // + // Ok(()) + // } + let timer_start = std::time::Instant::now(); + + let file = std::fs::File::create("raw_image_123.png").unwrap(); + let offset_x = 0.5; + let size_x = 0.5; + let offset_y = 0.5; + let size_y = 0.5; + let rect = "A1:AE55"; + let scale = 0.4; + + resize_image(file, rect, scale, offset_x, offset_y, size_x, size_y).await; + + let duration = timer_start.elapsed(); + println!("Operation completed in: {:.2?}", duration); + Ok(()) +} +async fn resize_image( + out: impl Write, + rect: &str, + scale: f32, + offset_x: f32, + offset_y: f32, + size_x: f32, + size_y: f32, +) { + let mut rect = rect.split(":"); const SIZE: u32 = 720; - let actual_size = (SIZE as f64 * SCALE).trunc() as u32; + let actual_size = (SIZE as f32 * scale).trunc() as u32; - // Open the image (a PhotonImage is returned) - // let max_col = 30; - // let max_row = 30; - let max_col = 31; - let max_row = 55; - let canvas_width = (max_row * actual_size) as usize; - let canvas_height = (max_col * actual_size) as usize; - let mut canvas = vec![0u8; canvas_width * canvas_height * 3]; - let mut resizer = Resizer::new(); - for i in 1..=max_col { - for j in 1..=max_row { - let filename = format!("{}{}{}.png", base_path, number_to_column(i), j); - // println!("{filename}"); + let start = rect.next().unwrap(); + let end = rect.next().unwrap(); - let data = Cursor::new(fs::read(filename).await.unwrap()); // .unwrap(); - let decoder = png::Decoder::new(data); - let mut reader = decoder.read_info().unwrap(); - let mut buf = vec![0; reader.output_buffer_size()]; - let _info = reader.next_frame(&mut buf).unwrap(); + let (start_col_s, start_row) = split_before_number(start); + let start_col = column_to_number(start_col_s); + let start_row = start_row.parse::().unwrap(); + let (end_col_s, end_row) = split_before_number(end); + let end_col = column_to_number(end_col_s); + let end_row = end_row.parse::().unwrap(); + let col_count = end_col - start_col + 1; + let row_count = end_row - start_row + 1; + let canvas_width = (row_count * actual_size) as usize; + let canvas_height = (col_count * actual_size) as usize; + let canvas = UnsafeVec::new(vec![0u8; canvas_width * canvas_height * 3]); - let src_img = Image::from_vec_u8(SIZE, SIZE, buf, PixelType::U8x3).unwrap(); - let mut img = Image::new(actual_size, actual_size, PixelType::U8x3); + let min_row = start_row + (row_count as f32 * offset_x).floor() as u32; + let max_row = start_row + (row_count as f32 * (offset_x + size_x)).floor() as u32; + let min_col = start_col + (col_count as f32 * offset_y).floor() as u32; + let max_col = start_col + (col_count as f32 * (offset_y + size_y)).floor() as u32; - resizer - .resize( - &src_img, - &mut img, - &ResizeOptions::new() - .resize_alg(ResizeAlg::Nearest) - .use_alpha(false), - ) - .unwrap(); - place_image( - &mut canvas, - canvas_width as usize, - canvas_height as usize, - &img.into_vec(), - (actual_size) as usize, - (actual_size) as usize, - (j * actual_size) as usize, - (i * actual_size) as usize, - ); + let mut handles = Vec::new(); + for i in start_col..=end_col { + for j in start_row..=end_row { + if i < min_col { + continue; + } + if i > max_col { + continue; + } + if j < min_row { + continue; + } + if j > max_row { + continue; + } + handles.push(add_to_canvas( + &canvas, + i, + start_col, + j, + start_row, + SIZE, + actual_size, + canvas_width, + canvas_height, + )); } } - let file = std::fs::File::create("raw_image.png").unwrap(); - let writer = std::io::BufWriter::new(file); + let results = join_all(handles).await; + + let writer = std::io::BufWriter::new(out); + + let start_x = (offset_x * canvas_width as f32).trunc() as usize; + let start_y = (offset_y * canvas_height as f32).trunc() as usize; + let end_x = ((offset_x + size_x) * canvas_width as f32).trunc() as usize; + let end_y = ((offset_y + size_y) * canvas_height as f32).trunc() as usize; + + let crop_width = end_x - start_x; + let crop_height = end_y - start_y; let mut encoder = png::Encoder::new( writer, - canvas_width.try_into().unwrap(), - canvas_height.try_into().unwrap(), + crop_width.try_into().unwrap(), + crop_height.try_into().unwrap(), ); encoder.set_color(png::ColorType::Rgb); encoder.set_depth(png::BitDepth::Eight); + println!("Writing img"); let mut png_writer = encoder.write_header().unwrap(); - png_writer.write_image_data(&canvas).unwrap(); - - let duration = start.elapsed(); - println!("Operation completed in: {:.2?}", duration); + png_writer + .write_image_data(&crop_rgb_image( + &canvas.to_vec(), + (actual_size * row_count) as usize, + (actual_size * col_count) as usize, + start_x, + start_y, + crop_width, + crop_height, + )) + .unwrap(); +} +fn split_before_number(input: &str) -> (&str, &str) { + let index = find_first_number_start(input); + (&input[..index], &input[index..]) +} +fn find_first_number_start(s: &str) -> usize { + for (i, c) in s.char_indices() { + if c.is_ascii_digit() { + return i; + } + } + s.len() } fn number_to_column(mut num: u32) -> String { let mut result = String::new(); @@ -83,8 +181,62 @@ fn number_to_column(mut num: u32) -> String { result } +fn column_to_number(s: &str) -> u32 { + let mut result = 0; + + for ch in s.chars() { + result = result * 26 + (ch as u32 - 'A' as u32 + 1); + } + + result +} +async fn add_to_canvas( + canvas: &UnsafeVec, + i: u32, + start_col: u32, + j: u32, + start_row: u32, + size: u32, + actual_size: u32, + canvas_width: usize, + canvas_height: usize, +) { + let mut resizer = Resizer::new(); + let base_path = "/mnt/c/Users/Formulatrix/Documents/Explorations/WebApplication1/tiles1705/"; + let filename = format!("{}{}{}.png", base_path, number_to_column(i), j); + // println!("{filename}"); + + let data = Cursor::new(fs::read(filename).await.unwrap()); // .unwrap(); + let decoder = png::Decoder::new(data); + let mut reader = decoder.read_info().unwrap(); + let mut buf = vec![0; reader.output_buffer_size()]; + let _info = reader.next_frame(&mut buf).unwrap(); + + let src_img = Image::from_vec_u8(size, size, buf, PixelType::U8x3).unwrap(); + let mut img = Image::new(actual_size, actual_size, PixelType::U8x3); + + resizer + .resize( + &src_img, + &mut img, + &ResizeOptions::new() + .resize_alg(ResizeAlg::Nearest) + .use_alpha(false), + ) + .unwrap(); + place_image( + &canvas, + canvas_width, + canvas_height, + &img.into_vec(), + (actual_size) as usize, + (actual_size) as usize, + ((j - start_row) * actual_size) as usize, + ((i - start_col) * actual_size) as usize, + ); +} fn place_image( - canvas: &mut [u8], + canvas: &UnsafeVec, canvas_width: usize, canvas_height: usize, image: &[u8], @@ -108,7 +260,74 @@ fn place_image( let image_end = image_start + image_width * channels; if canvas_end <= canvas.len() && image_end <= image.len() { - canvas[canvas_start..canvas_end].copy_from_slice(&image[image_start..image_end]); + unsafe { + canvas.range_mut(canvas_start..canvas_end, &image[image_start..image_end]); + } } } } + +fn crop_rgb_image( + img: &[u8], + width: usize, + height: usize, + start_x: usize, + start_y: usize, + crop_width: usize, + crop_height: usize, +) -> Vec { + let mut cropped = Vec::with_capacity(crop_width * crop_height * 3); + + let end_x = start_x + crop_width; + let end_y = start_y + crop_height; + for y in start_y..end_y { + let row_start = (y * width + start_x) * 3; + let row_end = (y * width + end_x) * 3; + cropped.extend_from_slice(&img[row_start..row_end]); + } + + cropped +} + +pub struct UnsafeVec { + data: UnsafeCell>, +} + +impl UnsafeVec { + pub fn new(initial: Vec) -> Self { + UnsafeVec { + data: UnsafeCell::new(initial), + } + } + + pub fn len(&self) -> usize { + unsafe { (*self.data.get()).len() } + } + + pub fn to_vec(self) -> Vec { + self.data.into_inner() + } + + pub unsafe fn range_mut>(&self, range: R, value: &[T]) { + unsafe { + let vec = &mut *self.data.get(); + + let start = match range.start_bound() { + Bound::Included(&s) => s, + Bound::Excluded(&s) => s + 1, + Bound::Unbounded => 0, + }; + + let end = match range.end_bound() { + Bound::Included(&e) => e + 1, + Bound::Excluded(&e) => e, + Bound::Unbounded => vec.len(), + }; + + &mut vec[start..end].copy_from_slice(value); + } + } +} + +unsafe impl Sync for UnsafeVec {} +unsafe impl Send for UnsafeVec {}