use std::collections::BTreeSet; use anyhow::{Context, Result}; use minedmap::{io::fs, types::*}; use super::common::*; pub struct TileMipmapper<'a> { config: &'a Config, } impl<'a> TileMipmapper<'a> { pub fn new(config: &'a Config) -> Self { TileMipmapper { config } } fn done(tiles: &TileCoordMap) -> bool { tiles .0 .iter() .all(|(z, xs)| (-1..=0).contains(z) && xs.iter().all(|x| (-1..=0).contains(x))) } fn map_coords(tiles: &TileCoordMap) -> TileCoordMap { let mut ret = TileCoordMap::default(); for (&z, xs) in &tiles.0 { for &x in xs { let xt = x >> 1; let zt = z >> 1; ret.0.entry(zt).or_default().insert(xt); } } ret } fn render_mipmap( &self, kind: TileKind, level: usize, coords: TileCoords, prev: &TileCoordMap, ) -> Result<()> where [P::Subpixel]: image::EncodableLayout, image::ImageBuffer>: Into, { const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32; let output_path = self.config.tile_path(kind, level, coords); println!( "Rendering mipmap tile {}", output_path .strip_prefix(&self.config.output_dir) .expect("tile path must be in output directory") .display(), ); let mut image: image::DynamicImage = image::ImageBuffer::>::new(N, N).into(); for (dx, dz) in [(0, 0), (0, 1), (1, 0), (1, 1)] { let source_coords = TileCoords { x: 2 * coords.x + dx, z: 2 * coords.z + dz, }; if !prev.contains(source_coords) { continue; } let source_path = self.config.tile_path(kind, level - 1, source_coords); let source = match image::open(&source_path) { Ok(source) => source, Err(err) => { eprintln!( "Failed to read source image {}: {}", source_path.display(), err, ); continue; } }; let resized = source.resize(N / 2, N / 2, image::imageops::FilterType::Triangle); image::imageops::overlay( &mut image, &resized, dx as i64 * (N / 2) as i64, dz as i64 * (N / 2) as i64, ); } fs::create_with_tmpfile(&output_path, |file| { image .write_to(file, image::ImageFormat::Png) .context("Failed to save image") }) } pub fn run(self, tiles: BTreeSet) -> Result> { let mut tile_stack = { let mut tile_map = TileCoordMap::default(); for TileCoords { x, z } in tiles { tile_map.0.entry(z).or_default().insert(x); } vec![tile_map] }; loop { let level = tile_stack.len(); let prev = &tile_stack[level - 1]; if Self::done(prev) { break; } fs::create_dir_all(&self.config.tile_dir(TileKind::Map, level))?; fs::create_dir_all(&self.config.tile_dir(TileKind::Lightmap, level))?; let next = Self::map_coords(prev); for (&z, xs) in &next.0 { for &x in xs { let coords = TileCoords { x, z }; self.render_mipmap::>(TileKind::Map, level, coords, prev)?; self.render_mipmap::>( TileKind::Lightmap, level, coords, prev, )?; } } tile_stack.push(next); } Ok(tile_stack) } }