minedmap: add mipmapping

This commit is contained in:
Matthias Schiffer 2023-07-02 21:32:40 +02:00
parent 86382772c3
commit 216aa6ceec
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
2 changed files with 128 additions and 0 deletions

View file

@ -1,5 +1,6 @@
mod common;
mod region_processor;
mod tile_mipmapper;
mod tile_renderer;
use std::path::PathBuf;
@ -9,6 +10,7 @@ use clap::Parser;
use common::Config;
use region_processor::RegionProcessor;
use tile_mipmapper::TileMipmapper;
use tile_renderer::TileRenderer;
#[derive(Debug, Parser)]
@ -25,6 +27,7 @@ fn main() -> Result<()> {
let regions = RegionProcessor::new(&config).run()?;
TileRenderer::new(&config).run(regions.iter().copied())?;
TileMipmapper::new(&config).run(regions)?;
Ok(())
}

View file

@ -0,0 +1,125 @@
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: &BTreeSet<TileCoords>) -> bool {
tiles
.into_iter()
.all(|TileCoords { x, z }| (-1..=0).contains(x) && (-1..=0).contains(z))
}
fn map_coords(tiles: &BTreeSet<TileCoords>) -> BTreeSet<TileCoords> {
let mut ret = BTreeSet::new();
for coords in tiles {
ret.insert(TileCoords {
x: coords.x >> 1,
z: coords.z >> 1,
});
}
ret
}
fn render_mipmap<P: image::PixelWithColorType>(
&self,
kind: TileKind,
level: usize,
coords: TileCoords,
prev: &BTreeSet<TileCoords>,
) -> Result<()>
where
[P::Subpixel]: image::EncodableLayout,
image::ImageBuffer<P, Vec<P::Subpixel>>: Into<image::DynamicImage>,
{
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::<P, Vec<P::Subpixel>>::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<TileCoords>) -> Result<Vec<BTreeSet<TileCoords>>> {
let mut tile_stack = vec![tiles];
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 &coords in &next {
self.render_mipmap::<image::Rgba<u8>>(TileKind::Map, level, coords, prev)?;
self.render_mipmap::<image::LumaA<u8>>(TileKind::Lightmap, level, coords, prev)?;
}
tile_stack.push(next);
}
Ok(tile_stack)
}
}