use std::collections::HashMap;
use std::collections::hash_map::Entry::{ Occupied, Vacant };
use std::fmt::{ Debug, Formatter, Error };
use std::path::{ Path, PathBuf };
use std::mem;
use gfx;
use image::{
self,
ImageBuffer,
RgbaImage,
DynamicImage,
ImageResult,
SubImage,
ImageError,
GenericImageView,
Pixel
};
pub use gfx_texture::{ Texture, ImageSize, TextureSettings };
fn load_rgba8(path: &Path) -> ImageResult<RgbaImage> {
image::open(path).map(|img| match img {
DynamicImage::ImageRgba8(img) => img,
img => img.to_rgba()
})
}
pub enum ColorMapError {
Img(ImageError),
Size(u32, u32, String)
}
impl Debug for ColorMapError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match *self {
ColorMapError::Img(ref e) => e.fmt(f),
ColorMapError::Size(w, h, ref path) =>
format!("ColorMap expected 256x256, found {}x{} in '{}'", w, h, path).fmt(f)
}
}
}
impl From<ImageError> for ColorMapError {
fn from(img_err: ImageError) -> Self {
ColorMapError::Img(img_err)
}
}
pub struct ColorMap(RgbaImage);
impl ColorMap {
pub fn from_path<P>(path: P) -> Result<Self, ColorMapError>
where P: AsRef<Path>
{
let img = try!(load_rgba8(path.as_ref()));
match img.dimensions() {
(256, 256) => Ok(ColorMap(img)),
(w, h) => Err(ColorMapError::Size(w, h, path.as_ref().display().to_string()))
}
}
pub fn get(&self, x: f32, y: f32) -> [u8; 3] {
let x = x.max(0.0).min(1.0);
let y = y.max(0.0).min(1.0);
let y = x * y;
let x = ((1.0 - x) * 255.0) as u8;
let y = ((1.0 - y) * 255.0) as u8;
let col = self.0.get_pixel(x as u32, y as u32).channels();
[col[0], col[1], col[2]]
}
}
pub struct AtlasBuilder {
image: RgbaImage,
path: PathBuf,
unit_width: u32,
unit_height: u32,
completed_tiles_size: u32,
position: u32,
tile_positions: HashMap<String, (u32, u32)>,
min_alpha_cache: HashMap<(u32, u32, u32, u32), u8>
}
impl AtlasBuilder {
pub fn new<P>(path: P, unit_width: u32, unit_height: u32) -> Self
where P: Into<PathBuf>
{
AtlasBuilder {
image: ImageBuffer::new(unit_width * 4, unit_height * 4),
path: path.into(),
unit_width: unit_width,
unit_height: unit_height,
completed_tiles_size: 0,
position: 0,
tile_positions: HashMap::new(),
min_alpha_cache: HashMap::new()
}
}
pub fn load(&mut self, name: &str) -> (u32, u32) {
if let Some(&pos) = self.tile_positions.get(name) {
return pos
}
let mut path = self.path.join(name);
path.set_extension("png");
let img = load_rgba8(&path).unwrap();
let (iw, ih) = img.dimensions();
assert!(iw == self.unit_width);
assert!((ih % self.unit_height) == 0);
if ih > self.unit_height {
println!("ignoring {} extra frames in '{}'", (ih / self.unit_height) - 1, name);
}
let (uw, uh) = (self.unit_width, self.unit_height);
let (w, h) = self.image.dimensions();
let size = self.completed_tiles_size;
if self.position == 0 && (uw * size >= w || uh * size >= h) {
let old = mem::replace(&mut self.image, ImageBuffer::new(w * 2, h * 2));
for ix in 0 .. w {
for iy in 0 .. h {
*self.image.get_pixel_mut(ix, iy) = old[(ix, iy)];
}
}
}
let (x, y) = if self.position < size {
(self.position, size)
} else {
(size, self.position - size)
};
self.position += 1;
if self.position >= size * 2 + 1 {
self.position = 0;
self.completed_tiles_size += 1;
}
{
let (x, y, w, h) = (x * uw, y * uh, uw, uh);
for ix in 0 .. w {
for iy in 0 .. h {
*self.image.get_pixel_mut(ix + x, iy + y) = img[(ix, iy)];
}
}
}
*match self.tile_positions.entry(name.to_string()) {
Occupied(entry) => entry.into_mut(),
Vacant(entry) => entry.insert((x * uw, y * uh))
}
}
pub fn min_alpha(&mut self, rect: [u32; 4]) -> u8 {
let x = rect[0];
let y = rect[1];
let w = rect[2];
let h = rect[3];
if let Some(&alpha) = self.min_alpha_cache.get(&(x, y, w, h)) {
return alpha
}
let tile = SubImage::new(&mut self.image, x, y, w, h);
let min_alpha = tile.pixels().map(|(_, _, p)| p[3]).min().unwrap_or(0);
self.min_alpha_cache.insert((x, y, w, h), min_alpha);
min_alpha
}
pub fn complete<R, F>(self, factory: &mut F) -> Texture<R>
where R: gfx::Resources, F: gfx::Factory<R>
{
let settings = TextureSettings::new().generate_mipmap(true);
Texture::from_image(factory, &self.image, &settings).unwrap()
}
}