use super::cell_type::*;
use super::source_2d::*;
use crate::errors::*;
use crate::mesh_2d::{Shape2D};
use image::{DynamicImage, GenericImageView, Pixel};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct Source2DFromImage {
width: usize,
height: usize,
cells: Vec<Vec<CellType>>,
}
impl Source2DFromImage {
pub const DEFAULT_THRESHOLD: f32 = 0.5;
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let img = image::open(path)
.map_err(|e| Error::report_bug(&format!("Failed to load image: {e}")))?;
Self::from_image(&img, Self::DEFAULT_THRESHOLD)
}
pub fn from_path_with_threshold<P: AsRef<Path>>(path: P, threshold: f32) -> Result<Self> {
let img = image::open(path)
.map_err(|e| Error::report_bug(&format!("Failed to load image: {e}")))?;
Self::from_image(&img, threshold)
}
pub fn from_image(img: &DynamicImage, threshold: f32) -> Result<Self> {
let width = img.width() as usize;
let height = img.height() as usize;
let mut cells = Vec::with_capacity(height);
for y in 0..height {
let mut row = Vec::with_capacity(width);
for x in 0..width {
let pixel = img.get_pixel(x as u32, y as u32);
let luma_u8 = pixel.to_luma()[0];
let luma = luma_u8 as f32 / 255.0; let cell_type = if luma < threshold {
CellType::WALL
} else {
CellType::FLOOR
};
row.push(cell_type);
}
cells.push(row);
}
Ok(Self {
width,
height,
cells,
})
}
}
impl Source2D for Source2DFromImage {
fn get(&self, x: usize, y: usize) -> Result<CellType> {
if y >= self.height || x >= self.width {
return Err(Error::invalid_xy(x, y));
}
Ok(self.cells[y][x])
}
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.height
}
}
impl Shape2D for Source2DFromImage {
fn shape(&self) -> (usize, usize) {
(self.width, self.height)
}
}
impl std::fmt::Display for Source2DFromImage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Source2DFromImage ({}x{}):", self.width(), self.height())?;
write!(f, "{}", crate::mesh_source::repr::repr_source_2d(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
use image::{ImageBuffer, Luma};
#[test]
fn test_from_image_basic() {
let mut img = ImageBuffer::new(4, 3);
img.put_pixel(0, 0, Luma([255]));
img.put_pixel(1, 0, Luma([255]));
img.put_pixel(2, 0, Luma([255]));
img.put_pixel(3, 0, Luma([255]));
img.put_pixel(0, 1, Luma([255])); img.put_pixel(1, 1, Luma([0])); img.put_pixel(2, 1, Luma([0])); img.put_pixel(3, 1, Luma([255]));
img.put_pixel(0, 2, Luma([255]));
img.put_pixel(1, 2, Luma([255]));
img.put_pixel(2, 2, Luma([255]));
img.put_pixel(3, 2, Luma([255]));
let dyn_img = DynamicImage::ImageLuma8(img);
let source = Source2DFromImage::from_image(&dyn_img, 0.5).unwrap();
assert_eq!(source.width(), 4);
assert_eq!(source.height(), 3);
assert_eq!(source.get(0, 0).unwrap(), CellType::FLOOR);
assert_eq!(source.get(1, 1).unwrap(), CellType::WALL);
assert_eq!(source.get(2, 1).unwrap(), CellType::WALL);
assert_eq!(source.get(3, 1).unwrap(), CellType::FLOOR);
}
#[test]
fn test_threshold() {
let mut img = ImageBuffer::new(2, 1);
img.put_pixel(0, 0, Luma([100])); img.put_pixel(1, 0, Luma([150]));
let dyn_img = DynamicImage::ImageLuma8(img);
let source = Source2DFromImage::from_image(&dyn_img, 0.5).unwrap();
assert_eq!(source.get(0, 0).unwrap(), CellType::WALL);
assert_eq!(source.get(1, 0).unwrap(), CellType::FLOOR);
}
#[test]
fn test_out_of_bounds() {
let img = ImageBuffer::new(2, 2);
let dyn_img = DynamicImage::ImageLuma8(img);
let source = Source2DFromImage::from_image(&dyn_img, 0.5).unwrap();
assert!(source.get(2, 0).is_err());
assert!(source.get(0, 2).is_err());
assert!(source.get(3, 3).is_err());
}
#[test]
fn test_from_path_testdata() {
use crate::mesh_source::repr_source_2d;
use crate::mesh_source::testutil::testdata_path;
let path = testdata_path("2d/a-32x24.jpg");
let source = Source2DFromImage::from_path(&path).unwrap();
assert_eq!(source.width(), 32);
assert_eq!(source.height(), 24);
let repr = repr_source_2d(&source);
let expected = "\
................................
................................
................................
................................
................................
...##############.#.............
...#############................
...#############................
...############..#..............
...############..#..............
...###########..##..............
...###########..###.............
...###########..................
...##########..####.............
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
";
assert_eq!(repr, expected, "Image representation should match expected pattern");
}
#[test]
fn test_from_path_with_threshold_testdata() {
use crate::mesh_source::testutil::testdata_path;
let path = testdata_path("2d/a-32x24.jpg");
let source_low = Source2DFromImage::from_path_with_threshold(&path, 0.1).unwrap();
let source_high = Source2DFromImage::from_path_with_threshold(&path, 0.9).unwrap();
let mut free_count_low = 0;
let mut free_count_high = 0;
for y in 0..source_low.height() {
for x in 0..source_low.width() {
if source_low.get(x, y).unwrap() == CellType::FLOOR {
free_count_low += 1;
}
if source_high.get(x, y).unwrap() == CellType::FLOOR {
free_count_high += 1;
}
}
}
assert!(
free_count_low >= free_count_high,
"Lower threshold should have more or equal free cells than higher threshold"
);
}
}