use bevy::prelude::*;
use image::{
DynamicImage, GenericImage, GenericImageView, ImageBuffer, ImageReader, Rgba, RgbaImage,
};
use itertools::Itertools;
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Clone, Copy)]
pub enum ImageError {
BadEnv,
FileNotFound,
DecodeFailed,
BackgroundNotDetermined,
NetNotFound,
NotAligned,
CopyError,
}
pub fn get_skybox(image_name: &str) -> Result<Image, ImageError> {
let root_path = std::env::var_os("CARGO_MANIFEST_DIR").ok_or(ImageError::BadEnv)?;
let path = Path::new(&root_path).join("assets").join(image_name);
let reader = ImageReader::open(path).map_err(|_| ImageError::FileNotFound)?;
let orig_image = reader.decode().map_err(|_| ImageError::DecodeFailed)?;
let orig_rgba = DynamicImage::ImageRgba8(orig_image.to_rgba8());
let meas = ImageMeasurements::find_measurements(&orig_rgba)?;
let shaped_image = meas.new_image(&orig_rgba)?;
Ok(shaped_image)
}
pub struct ImageMeasurements {
vec_x: Vec<u32>,
vec_y: Vec<u32>,
}
impl ImageMeasurements {
pub fn new_image(&self, old_image: &DynamicImage) -> Result<Image, ImageError> {
let side = self.measure_side_length();
let mut new_image = RgbaImage::new(side, side * 6);
self.copy_face(old_image, &mut new_image, side, 3, 1, 0)?;
self.copy_face(old_image, &mut new_image, side, 1, 1, 1)?;
self.copy_face(old_image, &mut new_image, side, 2, 0, 2)?;
self.copy_face(old_image, &mut new_image, side, 2, 2, 3)?;
self.copy_face(old_image, &mut new_image, side, 2, 1, 4)?;
self.copy_face(old_image, &mut new_image, side, 0, 1, 5)?;
let image = Image::from_dynamic(
image::DynamicImage::from(new_image),
true,
bevy::asset::RenderAssetUsages::all(),
);
Ok(image)
}
fn copy_face(
&self,
old_image: &DynamicImage,
new_image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
side: u32,
x_idx: usize,
y_idx: usize,
out_idx: usize,
) -> Result<(), ImageError> {
let offset_x = (self.vec_x[x_idx + 1] - self.vec_x[x_idx] - side) / 2;
let offset_y = (self.vec_y[y_idx + 1] - self.vec_y[y_idx] - side) / 2;
new_image
.copy_from(
&old_image
.view(
self.vec_x[x_idx] + offset_x,
self.vec_y[y_idx] + offset_y,
side,
side,
)
.to_image(),
0,
side * (out_idx as u32),
)
.map_err(|_| ImageError::CopyError)
}
pub fn find_measurements(rgb: &DynamicImage) -> Result<Self, ImageError> {
let background = find_background(&rgb)?;
let dy = rgb.height() / 6;
let mid_x_min = search_from_left(&rgb, background, dy * 3)?;
let mid_x_max = search_from_right(&rgb, background, dy * 3)?;
let top_x_min = search_from_left(&rgb, background, dy * 1)?;
let top_x_max = search_from_right(&rgb, background, dy * 1)?;
let bot_x_min = search_from_left(&rgb, background, dy * 5)?;
let bot_x_max = search_from_right(&rgb, background, dy * 5)?;
if (top_x_min as i32 - bot_x_min as i32).abs() > 8 {
return Err(ImageError::NotAligned);
}
if (top_x_max as i32 - bot_x_max as i32).abs() > 8 {
return Err(ImageError::NotAligned);
}
let short_x_min = (top_x_min + bot_x_min) / 2;
let short_x_max = (top_x_max + bot_x_max) / 2;
let vec_x = vec![
mid_x_min,
(short_x_min + mid_x_min) / 2,
short_x_min,
short_x_max,
mid_x_max,
];
let mut diff_x = vec_x
.as_slice()
.windows(2)
.map(|w| w[1] as i32 - w[0] as i32)
.collect::<Vec<i32>>();
diff_x.sort_unstable();
if diff_x[3] - diff_x[0] > 16 {
return Err(ImageError::NotAligned);
}
let mid_y_min = search_from_top(&rgb, background, (vec_x[2] + vec_x[3]) / 2)?;
let mid_y_max = search_from_bottom(&rgb, background, (vec_x[2] + vec_x[3]) / 2)?;
let left_y_min = search_from_top(&rgb, background, vec_x[1])?;
let left_y_max = search_from_bottom(&rgb, background, vec_x[1])?;
let right_y_min = search_from_top(&rgb, background, (vec_x[3] + vec_x[4]) / 2)?;
let right_y_max = search_from_bottom(&rgb, background, (vec_x[3] + vec_x[4]) / 2)?;
if (left_y_min as i32 - right_y_min as i32).abs() > 8 {
return Err(ImageError::NotAligned);
}
if (left_y_max as i32 - right_y_max as i32).abs() > 8 {
return Err(ImageError::NotAligned);
}
let short_y_min = (left_y_min + right_y_min) / 2;
let short_y_max = (left_y_max + right_y_max) / 2;
let vec_y = vec![mid_y_min, short_y_min, short_y_max, mid_y_max];
let mut diff_y = vec_y
.as_slice()
.windows(2)
.map(|w| w[1] as i32 - w[0] as i32)
.collect::<Vec<i32>>();
diff_y.sort_unstable();
if diff_y[2] - diff_y[0] > 16 {
return Err(ImageError::NotAligned);
}
Ok(ImageMeasurements { vec_x, vec_y })
}
fn measure_side_length(&self) -> u32 {
let min_x = self
.vec_x
.windows(2)
.map(|x| x[1] - x[0])
.min()
.expect("Four x intervals");
let min_y = self
.vec_y
.windows(2)
.map(|y| y[1] - y[0])
.min()
.expect("Three y intervals");
let side = min_x.min(min_y);
side
}
}
pub fn find_background(rgb: &DynamicImage) -> Result<Rgba<u8>, ImageError> {
let samples = (0..4)
.cartesian_product(0..2)
.map(|(x, y)| {
rgb.get_pixel(
(x * 2 + 1) * rgb.width() / 8,
(y * 4 + 1) * rgb.height() / 6,
)
})
.collect::<Vec<Rgba<u8>>>();
let mut sample_freq = HashMap::<Rgba<u8>, usize>::new();
for s in samples {
*sample_freq.entry(s).or_insert(0) += 1;
}
let mut sample_hist = sample_freq.drain().collect::<Vec<(Rgba<u8>, usize)>>();
sample_hist.sort_by(|a, b| (a.1).cmp(&b.1));
let background = sample_hist.iter().last().expect("Histogram");
if background.1 > 4 {
Ok(background.0)
} else {
Err(ImageError::BackgroundNotDetermined)
}
}
pub fn search_from_left(rgb: &DynamicImage, bg: Rgba<u8>, y: u32) -> Result<u32, ImageError> {
for x in 0..rgb.width() {
if rgb.get_pixel(x, y) != bg {
return Ok(x);
}
}
Err(ImageError::NetNotFound)
}
pub fn search_from_right(rgb: &DynamicImage, bg: Rgba<u8>, y: u32) -> Result<u32, ImageError> {
for x in (0..rgb.width()).rev() {
if rgb.get_pixel(x, y) != bg {
return Ok(x + 1);
}
}
Err(ImageError::NetNotFound)
}
pub fn search_from_top(rgb: &DynamicImage, bg: Rgba<u8>, x: u32) -> Result<u32, ImageError> {
for y in 0..rgb.height() {
if rgb.get_pixel(x, y) != bg {
return Ok(y);
}
}
Err(ImageError::NetNotFound)
}
pub fn search_from_bottom(rgb: &DynamicImage, bg: Rgba<u8>, x: u32) -> Result<u32, ImageError> {
for y in (0..rgb.height()).rev() {
if rgb.get_pixel(x, y) != bg {
return Ok(y + 1);
}
}
Err(ImageError::NetNotFound)
}