Hello Everyone, I am new to Rust.
I was making a photomosaic creator. (image made of smaller images as mosaic tiles).
I am not sure why this program runs slow (i know dev built runs slower than release one). It is way too slow.
Any help is really appreciated. Thanks.
Basic Idea:
- Loop over all the images in the given directory (via glob expression) and store their path and average rgb value.
- Create an empty image (tiles will be pasted here)
- Loop over the image by 50x50 tile. Get the average of the region and replace it (paste on empty image) with the closest matching tile from the 1st step.
What can I do to improve it. Is there a better way to do this.
The code used:
use image::DynamicImage;
use image::GenericImageView;
use image::Pixel;
// use image::Rgb;
use glob::glob;
struct Tile {
path: String,
avg_rgb: Vec<f32>,
}
pub struct Mosaic {
img: String,
tiles_dir: String,
tiles: Vec<Tile>,
tile_width: u32,
tile_height: u32,
}
impl Mosaic {
pub fn new(img: String, tiles_dir: String) -> Self {
Self {
img,
tiles_dir,
tiles: vec![],
tile_width: 50,
tile_height: 50,
}
}
fn distance(&self, rgb1: &Vec<f32>, rgb2: &Vec<f32>) -> f32 {
// returns distance between 2 rgb values
let r: f32 = rgb1[0] - rgb2[0];
let g: f32 = rgb1[1] - rgb2[1];
let b: f32 = rgb1[2] - rgb2[2];
let d: f32 = r.powf(2.0) + g.powf(2.0) + b.powf(2.0);
d.sqrt()
}
fn get_avg_rgb(&self, img: &DynamicImage) -> Vec<f32> {
let mut avg_r: f32 = 0.0;
let mut avg_g: f32 = 0.0;
let mut avg_b: f32 = 0.0;
let width = img.width();
let height = img.height();
let size = (width * height) as f32;
for w in 0..width {
for h in 0..height {
let rgb = img.get_pixel(w, h).to_rgb();
avg_r += rgb[0] as f32 / size;
avg_g += rgb[1] as f32 / size;
avg_b += rgb[2] as f32 / size;
}
}
vec![avg_r, avg_g, avg_b]
}
fn get_closest(&self, rgb: &Vec<f32>) -> usize {
let mut max = f32::MAX;
let mut index = 0;
for (i, tile) in self.tiles.iter().enumerate() {
let d = self.distance(rgb, &tile.avg_rgb);
if max >= d {
max = d;
index = i;
}
}
index
}
fn create_tiles(&mut self) {
let glob_expr = self.tiles_dir.to_owned() + "/*.*g";
for path in glob(&glob_expr).expect("Can't read Directory") {
let img_path = path.unwrap().to_str().unwrap().to_string();
let img = image::open(&img_path).unwrap();
self.tiles.push(Tile {
path: img_path,
avg_rgb: self.get_avg_rgb(&img),
})
}
}
fn create_mosaic(&mut self) {
self.create_tiles();
if self.tiles.len() == 0 {
println!(
"Tiles directory '{}' has no images",
self.tiles_dir
);
return;
}
println!("{} tiles created", self.tiles.len());
let n: u32 = 20; // for better output ; n = 5
let mut base_img = image::open(&self.img).unwrap().thumbnail(100, 100); //for better output, (500, 500)
let (width, height) = base_img.dimensions();
let new_width: u32 = width * self.tile_width / n;
let new_height: u32 = height * self.tile_height / n;
let mut new_img = DynamicImage::new_rgb8(new_width, new_height);
for w in (0..width).step_by(n as usize) {
for h in (0..height).step_by(n as usize) {
let piece = base_img.crop(w, h, n, n);
let piece_rgb = self.get_avg_rgb(&piece);
let index = self.get_closest(&piece_rgb);
let tile_img = image::open(&self.tiles[index].path)
.unwrap()
.thumbnail(self.tile_width, self.tile_height);
let x = w * self.tile_width / n;
let y = h * self.tile_height / n;
image::imageops::overlay(&mut new_img, &tile_img, x.into(), y.into());
}
}
match new_img.save("final.png") {
Ok(_t) => {
println!("Mosaic created")
}
Err(e) => {
println!("Error occured {}", e)
}
}
}
pub fn create(&mut self) {
self.create_mosaic();
}
}