use image::imageops::FilterType;
use image::{DynamicImage, GenericImageView, Rgb, RgbImage};
use std::collections::HashMap;
#[derive(Debug)]
pub struct Tile {
img: RgbImage,
avg: Rgb<u8>,
}
impl Tile {
pub fn dist_to(&self, px: &Rgb<u8>) -> f32 {
let p_r = px.0[0] as i32;
let p_g = px.0[1] as i32;
let p_b = px.0[2] as i32;
let q_r = self.avg.0[0] as i32;
let q_g = self.avg.0[1] as i32;
let q_b = self.avg.0[2] as i32;
(((p_r - q_r).pow(2) + (p_g - q_g).pow(2) + (p_b - q_b).pow(2)) as f32).sqrt()
}
pub fn img(&self) -> &RgbImage {
&self.img
}
pub fn side_len(&self) -> u32 {
self.img.dimensions().0
}
}
impl From<RgbImage> for Tile {
fn from(img: RgbImage) -> Self {
let avg_px_color = {
let mut tot_r = 0;
let mut tot_g = 0;
let mut tot_b = 0;
for px in img.pixels() {
tot_r += px.0[0] as usize;
tot_g += px.0[1] as usize;
tot_b += px.0[2] as usize;
}
let num_px = img.pixels().len();
Rgb([
(tot_r / num_px) as u8,
(tot_g / num_px) as u8,
(tot_b / num_px) as u8,
])
};
Self {
img,
avg: avg_px_color,
}
}
}
#[derive(Debug)]
pub struct TileSet {
tiles: Vec<Tile>,
}
impl TileSet {
pub fn tile_side_len(&self) -> u32 {
self.tiles[0].side_len()
}
pub fn map_to<'a>(&self, img: &'a RgbImage) -> HashMap<&'a Rgb<u8>, &Tile> {
let mut map = HashMap::new();
for px in img.pixels() {
if map.contains_key(px) {
continue; }
map.insert(px, self.closest_tile(px));
}
map
}
pub fn scale_tiles(&mut self, s: u32) {
self.tiles = self
.tiles
.iter()
.map(|t| {
let dyn_img = DynamicImage::ImageRgb8(t.img().clone());
Tile::from(dyn_img.resize_exact(s, s, FilterType::Triangle).to_rgb8())
})
.collect();
}
fn closest_tile(&self, px: &Rgb<u8>) -> &Tile {
let mut min_idx = 0;
for (i, t) in self.tiles.iter().enumerate() {
if t.dist_to(px) < self.tiles[min_idx].dist_to(px) {
min_idx = i;
}
}
&self.tiles[min_idx]
}
}
impl From<&Vec<DynamicImage>> for TileSet {
fn from(imgs: &Vec<DynamicImage>) -> Self {
let s = imgs
.iter()
.map(|img| {
let (w, h) = img.dimensions();
if w < h {
w
} else {
h
}
})
.min()
.unwrap();
let imgs: Vec<RgbImage> = imgs
.iter()
.map(|img| img.resize_exact(s, s, FilterType::Triangle).to_rgb8())
.collect();
Self {
tiles: imgs.iter().map(|img| Tile::from(img.clone())).collect(),
}
}
}