Merge pull request #62 from neocturne/webp

core, viewer: add support for WebP output
This commit is contained in:
Matthias Schiffer 2025-01-11 01:50:36 +01:00 committed by GitHub
commit ec309dc15f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 232 additions and 20 deletions

View file

@ -2,6 +2,10 @@
## [Unreleased] - ReleaseDate
### Added
- Added support for rendering tiles WebP format using the `--image-format` option
## [2.3.1] - 2025-01-06
### Fixed

177
Cargo.lock generated
View file

@ -125,9 +125,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
[[package]]
name = "bytemuck"
@ -313,6 +313,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "erased-serde"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.10"
@ -461,10 +471,21 @@ checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
dependencies = [
"bytemuck",
"byteorder-lite",
"image-webp",
"num-traits",
"png",
]
[[package]]
name = "image-webp"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f"
dependencies = [
"byteorder-lite",
"quick-error",
]
[[package]]
name = "indexmap"
version = "2.7.0"
@ -546,9 +567,12 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.22"
version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "3d6ea2a48c204030ee31a7d7fc72c93294c92fe87ecb1789881c9543516e1a0d"
dependencies = [
"value-bag",
]
[[package]]
name = "lru"
@ -800,6 +824,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.38"
@ -850,7 +880,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
]
[[package]]
@ -900,7 +930,7 @@ version = "0.38.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"errno",
"libc",
"linux-raw-sys",
@ -948,6 +978,15 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_fmt"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4"
dependencies = [
"serde",
]
[[package]]
name = "serde_json"
version = "1.0.135"
@ -1009,10 +1048,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.95"
name = "sval"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
checksum = "f6dc0f9830c49db20e73273ffae9b5240f63c42e515af1da1fceefb69fceafd8"
[[package]]
name = "sval_buffer"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "429922f7ad43c0ef8fd7309e14d750e38899e32eb7e8da656ea169dd28ee212f"
dependencies = [
"sval",
"sval_ref",
]
[[package]]
name = "sval_dynamic"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f16ff5d839396c11a30019b659b0976348f3803db0626f736764c473b50ff4"
dependencies = [
"sval",
]
[[package]]
name = "sval_fmt"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c01c27a80b6151b0557f9ccbe89c11db571dc5f68113690c1e028d7e974bae94"
dependencies = [
"itoa",
"ryu",
"sval",
]
[[package]]
name = "sval_json"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0deef63c70da622b2a8069d8600cf4b05396459e665862e7bdb290fd6cf3f155"
dependencies = [
"itoa",
"ryu",
"sval",
]
[[package]]
name = "sval_nested"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a39ce5976ae1feb814c35d290cf7cf8cd4f045782fe1548d6bc32e21f6156e9f"
dependencies = [
"sval",
"sval_buffer",
"sval_ref",
]
[[package]]
name = "sval_ref"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7c6ee3751795a728bc9316a092023529ffea1783499afbc5c66f5fabebb1fa"
dependencies = [
"sval",
]
[[package]]
name = "sval_serde"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a5572d0321b68109a343634e3a5d576bf131b82180c6c442dee06349dfc652a"
dependencies = [
"serde",
"sval",
"sval_nested",
]
[[package]]
name = "syn"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
@ -1107,6 +1224,12 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "typeid"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e"
[[package]]
name = "unicode-ident"
version = "1.0.14"
@ -1125,6 +1248,42 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2"
dependencies = [
"value-bag-serde1",
"value-bag-sval2",
]
[[package]]
name = "value-bag-serde1"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b"
dependencies = [
"erased-serde",
"serde",
"serde_fmt",
]
[[package]]
name = "value-bag-sval2"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a"
dependencies = [
"sval",
"sval_buffer",
"sval_dynamic",
"sval_fmt",
"sval_json",
"sval_ref",
"sval_serde",
]
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -44,7 +44,7 @@ enum-map = "2.7.3"
fastnbt = "2.3.2"
futures-util = "0.3.28"
git-version = "0.3.5"
image = { version = "0.25.1", default-features = false, features = ["png"] }
image = { version = "0.25.1", default-features = false, features = ["png", "webp"] }
indexmap = { version = "2.0.0", features = ["serde"] }
lru = "0.12.0"
minedmap-nbt = { version = "0.1.1", path = "crates/nbt", default-features = false }

View file

@ -55,9 +55,18 @@ a proper webserver like [nginx](https://nginx.org/) or upload the viewer togethe
the generated map files to public webspace to make the map available to others.
If you are uploading the directory to a remote webserver, you do not need to upload the
`<viewer>/data/processed` directory, as that is only used locally to allow processing
`<viewer>/data/processed` directory, as it is only used locally to allow processing
updates more quickly.
### Image formats
MinedMap renders map tiles as PNG by default. Pass `--image-format webp` to select
WebP instead. For typical Minecraft worlds, using WebP reduces file sizes by 10-15%
without increasing processing time.
MinedMap always uses lossless compression for tile images, regardless of the
image format.
### Signs
![Sign screenshot](https://raw.githubusercontent.com/neocturne/MinedMap/e5d9c813ba3118d04dc7e52e3dc6f48808a69120/docs/images/signs.png)

View file

@ -7,6 +7,7 @@ use std::{
};
use anyhow::{Context, Result};
use clap::ValueEnum;
use indexmap::IndexSet;
use regex::{Regex, RegexSet};
use serde::{Deserialize, Serialize};
@ -150,6 +151,8 @@ pub struct Config {
pub viewer_info_path: PathBuf,
/// Path of viewer entities file
pub viewer_entities_path: PathBuf,
/// Format of generated map tiles
pub image_format: ImageFormat,
/// Sign text filter patterns
pub sign_patterns: RegexSet,
/// Sign text transformation pattern
@ -189,6 +192,7 @@ impl Config {
entities_path_final,
viewer_info_path,
viewer_entities_path,
image_format: args.image_format,
sign_patterns,
sign_transforms,
})
@ -264,14 +268,39 @@ impl Config {
[&self.output_dir, Path::new(&dir)].iter().collect()
}
/// Returns the file extension for the configured image format
pub fn tile_extension(&self) -> &'static str {
match self.image_format {
ImageFormat::Png => "png",
ImageFormat::Webp => "webp",
}
}
/// Returns the configurured image format for the image library
pub fn tile_image_format(&self) -> image::ImageFormat {
match self.image_format {
ImageFormat::Png => image::ImageFormat::Png,
ImageFormat::Webp => image::ImageFormat::WebP,
}
}
/// Constructs the path of an output tile image
pub fn tile_path(&self, kind: TileKind, level: usize, coords: TileCoords) -> PathBuf {
let filename = coord_filename(coords, "png");
let filename = coord_filename(coords, self.tile_extension());
let dir = self.tile_dir(kind, level);
[Path::new(&dir), Path::new(&filename)].iter().collect()
}
}
/// Format of generated map tiles
#[derive(Debug, Clone, Copy, Default, ValueEnum)]
pub enum ImageFormat {
/// Generate PNG images
#[default]
Png,
/// Generate WebP images
Webp,
}
/// Copies a chunk image into a region tile
pub fn overlay_chunk<I, J>(image: &mut I, chunk: &J, coords: ChunkCoords)
where

View file

@ -61,6 +61,8 @@ struct Metadata<'t> {
spawn: Spawn,
/// Enabled MinedMap features
features: Features,
/// Format of generated map tiles
tile_extension: &'static str,
}
/// Viewer entity JSON data structure
@ -205,6 +207,7 @@ impl<'a> MetadataWriter<'a> {
mipmaps: Vec::new(),
spawn: Self::spawn(&level_dat),
features,
tile_extension: self.config.tile_extension(),
};
for tile_map in self.tiles.iter() {

View file

@ -16,7 +16,7 @@ use anyhow::{Context, Result};
use clap::Parser;
use git_version::git_version;
use common::Config;
use common::{Config, ImageFormat};
use metadata_writer::MetadataWriter;
use region_processor::RegionProcessor;
use tile_mipmapper::TileMipmapper;
@ -47,6 +47,9 @@ pub struct Args {
/// Enable verbose messages
#[arg(short, long)]
pub verbose: bool,
/// Format of generated map tiles
#[arg(long, value_enum, default_value_t)]
pub image_format: ImageFormat,
/// Prefix for text of signs to show on the map
#[arg(long)]
pub sign_prefix: Vec<String>,

View file

@ -79,6 +79,8 @@ struct SingleRegionProcessor<'a> {
lightmap: image::GrayAlphaImage,
/// Processed entity intermediate data
entities: ProcessedEntities,
/// Format of generated map tiles
image_format: image::ImageFormat,
/// True if any unknown block or biome types were encountered during processing
has_unknown: bool,
}
@ -127,6 +129,7 @@ impl<'a> SingleRegionProcessor<'a> {
processed_region,
lightmap,
entities,
image_format: processor.config.tile_image_format(),
has_unknown: false,
})
}
@ -179,7 +182,7 @@ impl<'a> SingleRegionProcessor<'a> {
self.input_timestamp,
|file| {
self.lightmap
.write_to(file, image::ImageFormat::Png)
.write_to(file, self.image_format)
.context("Failed to save image")
},
)

View file

@ -144,7 +144,7 @@ where
}
image
.write_to(file, image::ImageFormat::Png)
.write_to(file, self.config.tile_image_format())
.context("Failed to save image")
}
}

View file

@ -304,7 +304,7 @@ impl<'a> TileRenderer<'a> {
processed_timestamp,
|file| {
image
.write_to(file, image::ImageFormat::Png)
.write_to(file, self.config.tile_image_format())
.context("Failed to save image")
},
)?;

View file

@ -73,7 +73,7 @@ function signIcon(material, kind) {
}
const MinedMapLayer = L.TileLayer.extend({
initialize: function (mipmaps, layer) {
initialize: function (mipmaps, layer, tile_extension) {
L.TileLayer.prototype.initialize.call(this, '', {
detectRetina: true,
tileSize: 512,
@ -88,6 +88,7 @@ const MinedMapLayer = L.TileLayer.extend({
this.mipmaps = mipmaps;
this.layer = layer;
this.ext = tile_extension;
},
createTile: function (coords, done) {
@ -112,7 +113,7 @@ const MinedMapLayer = L.TileLayer.extend({
return L.Util.emptyImageUrl;
return 'data/'+this.layer+'/'+z+'/r.'+coords.x+'.'+coords.y+'.png';
return `data/${this.layer}/${z}/r.${coords.x}.${coords.y}.${this.ext}`;
},
});
@ -332,6 +333,7 @@ window.createMap = function () {
const res = await response.json();
const {mipmaps, spawn} = res;
const features = res.features || {};
const tile_extension = res.tile_extension || 'png';
const updateParams = function () {
const args = parseHash();
@ -369,10 +371,10 @@ window.createMap = function () {
const overlayMaps = {};
const mapLayer = new MinedMapLayer(mipmaps, 'map');
const mapLayer = new MinedMapLayer(mipmaps, 'map', tile_extension);
mapLayer.addTo(map);
const lightLayer = new MinedMapLayer(mipmaps, 'light');
const lightLayer = new MinedMapLayer(mipmaps, 'light', tile_extension);
overlayMaps['Illumination'] = lightLayer;
if (params.light)
map.addLayer(lightLayer);