mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-04-20 11:35:07 +02:00
core, viewer: add support for WebP output
WebP can be selected by passing `--image-format webp` on the command line. For typical Minecraft worlds, this results in a size reduction of 10-15% without increasing processing time.
This commit is contained in:
parent
bb11b29e92
commit
c23b53a8c3
11 changed files with 81 additions and 11 deletions
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased] - ReleaseDate
|
## [Unreleased] - ReleaseDate
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for rendering tiles WebP format using the `--image-format` option
|
||||||
|
|
||||||
## [2.3.1] - 2025-01-06
|
## [2.3.1] - 2025-01-06
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -461,10 +461,21 @@ checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
|
"image-webp",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png",
|
"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]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
|
@ -800,6 +811,12 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.38"
|
version = "1.0.38"
|
||||||
|
|
|
@ -44,7 +44,7 @@ enum-map = "2.7.3"
|
||||||
fastnbt = "2.3.2"
|
fastnbt = "2.3.2"
|
||||||
futures-util = "0.3.28"
|
futures-util = "0.3.28"
|
||||||
git-version = "0.3.5"
|
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"] }
|
indexmap = { version = "2.0.0", features = ["serde"] }
|
||||||
lru = "0.12.0"
|
lru = "0.12.0"
|
||||||
minedmap-nbt = { version = "0.1.1", path = "crates/nbt", default-features = false }
|
minedmap-nbt = { version = "0.1.1", path = "crates/nbt", default-features = false }
|
||||||
|
|
11
README.md
11
README.md
|
@ -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.
|
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
|
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.
|
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
|
### Signs
|
||||||
|
|
||||||

|

|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use clap::ValueEnum;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use regex::{Regex, RegexSet};
|
use regex::{Regex, RegexSet};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -150,6 +151,8 @@ pub struct Config {
|
||||||
pub viewer_info_path: PathBuf,
|
pub viewer_info_path: PathBuf,
|
||||||
/// Path of viewer entities file
|
/// Path of viewer entities file
|
||||||
pub viewer_entities_path: PathBuf,
|
pub viewer_entities_path: PathBuf,
|
||||||
|
/// Format of generated map tiles
|
||||||
|
pub image_format: ImageFormat,
|
||||||
/// Sign text filter patterns
|
/// Sign text filter patterns
|
||||||
pub sign_patterns: RegexSet,
|
pub sign_patterns: RegexSet,
|
||||||
/// Sign text transformation pattern
|
/// Sign text transformation pattern
|
||||||
|
@ -189,6 +192,7 @@ impl Config {
|
||||||
entities_path_final,
|
entities_path_final,
|
||||||
viewer_info_path,
|
viewer_info_path,
|
||||||
viewer_entities_path,
|
viewer_entities_path,
|
||||||
|
image_format: args.image_format,
|
||||||
sign_patterns,
|
sign_patterns,
|
||||||
sign_transforms,
|
sign_transforms,
|
||||||
})
|
})
|
||||||
|
@ -264,14 +268,39 @@ impl Config {
|
||||||
[&self.output_dir, Path::new(&dir)].iter().collect()
|
[&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
|
/// Constructs the path of an output tile image
|
||||||
pub fn tile_path(&self, kind: TileKind, level: usize, coords: TileCoords) -> PathBuf {
|
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);
|
let dir = self.tile_dir(kind, level);
|
||||||
[Path::new(&dir), Path::new(&filename)].iter().collect()
|
[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
|
/// Copies a chunk image into a region tile
|
||||||
pub fn overlay_chunk<I, J>(image: &mut I, chunk: &J, coords: ChunkCoords)
|
pub fn overlay_chunk<I, J>(image: &mut I, chunk: &J, coords: ChunkCoords)
|
||||||
where
|
where
|
||||||
|
|
|
@ -61,6 +61,8 @@ struct Metadata<'t> {
|
||||||
spawn: Spawn,
|
spawn: Spawn,
|
||||||
/// Enabled MinedMap features
|
/// Enabled MinedMap features
|
||||||
features: Features,
|
features: Features,
|
||||||
|
/// Format of generated map tiles
|
||||||
|
tile_extension: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Viewer entity JSON data structure
|
/// Viewer entity JSON data structure
|
||||||
|
@ -205,6 +207,7 @@ impl<'a> MetadataWriter<'a> {
|
||||||
mipmaps: Vec::new(),
|
mipmaps: Vec::new(),
|
||||||
spawn: Self::spawn(&level_dat),
|
spawn: Self::spawn(&level_dat),
|
||||||
features,
|
features,
|
||||||
|
tile_extension: self.config.tile_extension(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for tile_map in self.tiles.iter() {
|
for tile_map in self.tiles.iter() {
|
||||||
|
|
|
@ -16,7 +16,7 @@ use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use git_version::git_version;
|
use git_version::git_version;
|
||||||
|
|
||||||
use common::Config;
|
use common::{Config, ImageFormat};
|
||||||
use metadata_writer::MetadataWriter;
|
use metadata_writer::MetadataWriter;
|
||||||
use region_processor::RegionProcessor;
|
use region_processor::RegionProcessor;
|
||||||
use tile_mipmapper::TileMipmapper;
|
use tile_mipmapper::TileMipmapper;
|
||||||
|
@ -47,6 +47,9 @@ pub struct Args {
|
||||||
/// Enable verbose messages
|
/// Enable verbose messages
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub verbose: bool,
|
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
|
/// Prefix for text of signs to show on the map
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub sign_prefix: Vec<String>,
|
pub sign_prefix: Vec<String>,
|
||||||
|
|
|
@ -79,6 +79,8 @@ struct SingleRegionProcessor<'a> {
|
||||||
lightmap: image::GrayAlphaImage,
|
lightmap: image::GrayAlphaImage,
|
||||||
/// Processed entity intermediate data
|
/// Processed entity intermediate data
|
||||||
entities: ProcessedEntities,
|
entities: ProcessedEntities,
|
||||||
|
/// Format of generated map tiles
|
||||||
|
image_format: image::ImageFormat,
|
||||||
/// True if any unknown block or biome types were encountered during processing
|
/// True if any unknown block or biome types were encountered during processing
|
||||||
has_unknown: bool,
|
has_unknown: bool,
|
||||||
}
|
}
|
||||||
|
@ -127,6 +129,7 @@ impl<'a> SingleRegionProcessor<'a> {
|
||||||
processed_region,
|
processed_region,
|
||||||
lightmap,
|
lightmap,
|
||||||
entities,
|
entities,
|
||||||
|
image_format: processor.config.tile_image_format(),
|
||||||
has_unknown: false,
|
has_unknown: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -179,7 +182,7 @@ impl<'a> SingleRegionProcessor<'a> {
|
||||||
self.input_timestamp,
|
self.input_timestamp,
|
||||||
|file| {
|
|file| {
|
||||||
self.lightmap
|
self.lightmap
|
||||||
.write_to(file, image::ImageFormat::Png)
|
.write_to(file, self.image_format)
|
||||||
.context("Failed to save image")
|
.context("Failed to save image")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -144,7 +144,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
image
|
image
|
||||||
.write_to(file, image::ImageFormat::Png)
|
.write_to(file, self.config.tile_image_format())
|
||||||
.context("Failed to save image")
|
.context("Failed to save image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,7 +304,7 @@ impl<'a> TileRenderer<'a> {
|
||||||
processed_timestamp,
|
processed_timestamp,
|
||||||
|file| {
|
|file| {
|
||||||
image
|
image
|
||||||
.write_to(file, image::ImageFormat::Png)
|
.write_to(file, self.config.tile_image_format())
|
||||||
.context("Failed to save image")
|
.context("Failed to save image")
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -73,7 +73,7 @@ function signIcon(material, kind) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MinedMapLayer = L.TileLayer.extend({
|
const MinedMapLayer = L.TileLayer.extend({
|
||||||
initialize: function (mipmaps, layer) {
|
initialize: function (mipmaps, layer, tile_extension) {
|
||||||
L.TileLayer.prototype.initialize.call(this, '', {
|
L.TileLayer.prototype.initialize.call(this, '', {
|
||||||
detectRetina: true,
|
detectRetina: true,
|
||||||
tileSize: 512,
|
tileSize: 512,
|
||||||
|
@ -88,6 +88,7 @@ const MinedMapLayer = L.TileLayer.extend({
|
||||||
|
|
||||||
this.mipmaps = mipmaps;
|
this.mipmaps = mipmaps;
|
||||||
this.layer = layer;
|
this.layer = layer;
|
||||||
|
this.ext = tile_extension;
|
||||||
},
|
},
|
||||||
|
|
||||||
createTile: function (coords, done) {
|
createTile: function (coords, done) {
|
||||||
|
@ -112,7 +113,7 @@ const MinedMapLayer = L.TileLayer.extend({
|
||||||
return L.Util.emptyImageUrl;
|
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 res = await response.json();
|
||||||
const {mipmaps, spawn} = res;
|
const {mipmaps, spawn} = res;
|
||||||
const features = res.features || {};
|
const features = res.features || {};
|
||||||
|
const tile_extension = res.tile_extension || 'png';
|
||||||
|
|
||||||
const updateParams = function () {
|
const updateParams = function () {
|
||||||
const args = parseHash();
|
const args = parseHash();
|
||||||
|
@ -369,10 +371,10 @@ window.createMap = function () {
|
||||||
|
|
||||||
const overlayMaps = {};
|
const overlayMaps = {};
|
||||||
|
|
||||||
const mapLayer = new MinedMapLayer(mipmaps, 'map');
|
const mapLayer = new MinedMapLayer(mipmaps, 'map', tile_extension);
|
||||||
mapLayer.addTo(map);
|
mapLayer.addTo(map);
|
||||||
|
|
||||||
const lightLayer = new MinedMapLayer(mipmaps, 'light');
|
const lightLayer = new MinedMapLayer(mipmaps, 'light', tile_extension);
|
||||||
overlayMaps['Illumination'] = lightLayer;
|
overlayMaps['Illumination'] = lightLayer;
|
||||||
if (params.light)
|
if (params.light)
|
||||||
map.addLayer(lightLayer);
|
map.addLayer(lightLayer);
|
||||||
|
|
Loading…
Add table
Reference in a new issue