diff --git a/debian/config.yaml b/debian/config.yaml index 30b9ad27e..39b34c953 100644 --- a/debian/config.yaml +++ b/debian/config.yaml @@ -40,6 +40,7 @@ cache_size_mb: 512 # - /path/to/sprites_dir # sources: # sprite1: /path/to/sprites_dir2 +# make_sdf: false # fonts: # - /path/to/font/file.ttf diff --git a/docs/src/config-file.md b/docs/src/config-file.md index 11d7a5402..95caa4222 100644 --- a/docs/src/config-file.md +++ b/docs/src/config-file.md @@ -207,6 +207,7 @@ sprites: sources: # SVG images in this directory will be published as a "my_sprites" sprite source my_sprites: /path/to/some_dir + make_sdf: false # Font configuration fonts: diff --git a/docs/src/sources-sprites.md b/docs/src/sources-sprites.md index 5b24cb8e1..679b6d3d7 100644 --- a/docs/src/sources-sprites.md +++ b/docs/src/sources-sprites.md @@ -57,6 +57,13 @@ configuration to the config file. martin --sprite /path/to/sprite_a --sprite /path/to/other/sprite_b ``` +To use generate Signed Distance Fields (SDFs) images instead, the `--make_sdf` Option can be added. +SDF Images allow their color to be set at runtime in the map rendering engine. + +```bash +martin --sprite /path/to/sprite_a --sprite /path/to/other/sprite_b --make_sdf +``` + ### Configuring with Config File A sprite directory can be configured from the config file with the `sprite` key, similar to @@ -71,6 +78,10 @@ sprites: sources: # SVG images in this directory will be published under the sprite_id "my_sprites" my_sprites: /path/to/some_dir + # Tells Martin to handle sprites as Signed Distance Fields (SDFs) + # SDF Images allow their color to be set at runtime in the map rendering engine. + # Defaults to `false`. + make_sdf: false ``` The sprites are now avaliable at `/sprite/my_images,some_dir.png`/ ... diff --git a/martin/src/args/root.rs b/martin/src/args/root.rs index 7bcda8103..0ab29e3ee 100644 --- a/martin/src/args/root.rs +++ b/martin/src/args/root.rs @@ -59,6 +59,12 @@ pub struct ExtraArgs { /// Export a directory with SVG files as a sprite source. Can be specified multiple times. #[arg(short, long)] pub sprite: Vec, + /// Tells Martin to handle sprites as Signed Distance Fields (SDFs) + /// SDF Images allow their color to be set at runtime in the map rendering engine. + /// + /// Defaults to `false`. + #[arg(long)] + pub make_sdf: bool, /// Export a font file or a directory with font files as a font source (recursive). Can be specified multiple times. #[arg(short, long)] pub font: Vec, @@ -109,7 +115,14 @@ impl Args { #[cfg(feature = "sprites")] if !self.extras.sprite.is_empty() { - config.sprites = FileConfigEnum::new(self.extras.sprite); + config.sprites = FileConfigEnum::new_extended( + self.extras.sprite, + std::collections::BTreeMap::new(), + crate::sprites::SpriteConfig { + make_sdf: self.extras.make_sdf, + ..Default::default() + }, + ); } if !self.extras.font.is_empty() { diff --git a/martin/src/sprites/mod.rs b/martin/src/sprites/mod.rs index 07e1c769a..fd37d9413 100644 --- a/martin/src/sprites/mod.rs +++ b/martin/src/sprites/mod.rs @@ -60,6 +60,11 @@ pub type SpriteCatalog = BTreeMap; #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct SpriteConfig { + /// Tells Martin to handle sprites as Signed Distance Fields (SDFs) + /// SDF Images allow their color to be set at runtime in the map rendering engine. + /// + /// Defaults to `false`. + pub make_sdf: bool, #[serde(flatten)] pub unrecognized: UnrecognizedValues, } @@ -71,7 +76,14 @@ impl ConfigExtras for SpriteConfig { } #[derive(Debug, Clone, Default)] -pub struct SpriteSources(HashMap); +pub struct SpriteSources { + sources: HashMap, + /// Tells Martin to handle sprites as Signed Distance Fields (SDFs) + /// SDF Images allow their color to be set at runtime in the map rendering engine. + /// + /// Defaults to `false`. + make_sdf: bool, +} impl SpriteSources { pub fn resolve(config: &mut FileConfigEnum) -> FileResult { @@ -79,7 +91,10 @@ impl SpriteSources { return Ok(Self::default()); }; - let mut results = Self::default(); + let mut results = Self { + make_sdf: cfg.custom.make_sdf, + ..Default::default() + }; let mut directories = Vec::new(); let mut configs = BTreeMap::new(); @@ -110,7 +125,7 @@ impl SpriteSources { pub fn get_catalog(&self) -> SpriteResult { // TODO: all sprite generation should be pre-cached let mut entries = SpriteCatalog::new(); - for (id, source) in &self.0 { + for (id, source) in &self.sources { let paths = get_svg_input_paths(&source.path, true) .map_err(|e| SpriteProcessingError(e, source.path.clone()))?; let mut images = Vec::with_capacity(paths.len()); @@ -131,7 +146,7 @@ impl SpriteSources { if path.is_file() { warn!("Ignoring non-directory sprite source {id} from {disp_path}"); } else { - match self.0.entry(id) { + match self.sources.entry(id) { Entry::Occupied(v) => { warn!("Ignoring duplicate sprite source {} from {disp_path} because it was already configured for {}", v.key(), v.get().path.display()); @@ -156,13 +171,13 @@ impl SpriteSources { let sprite_ids = ids .split(',') .map(|id| { - self.0 + self.sources .get(id) .ok_or_else(|| SpriteError::SpriteNotFound(id.to_string())) }) .collect::>>()?; - get_spritesheet(sprite_ids.into_iter(), dpi).await + get_spritesheet(sprite_ids.into_iter(), dpi, self.make_sdf).await } } @@ -194,6 +209,7 @@ async fn parse_sprite( pub async fn get_spritesheet( sources: impl Iterator, pixel_ratio: u8, + make_sdf: bool, ) -> SpriteResult { // Asynchronously load all SVG files from the given sources let mut futures = Vec::new(); @@ -208,6 +224,9 @@ pub async fn get_spritesheet( } let sprites = try_join_all(futures).await?; let mut builder = SpritesheetBuilder::new(); + if make_sdf { + builder.make_sdf(); + } builder.sprites(sprites.into_iter().collect()); // TODO: decide if this is needed and/or configurable @@ -231,27 +250,35 @@ mod tests { PathBuf::from("../tests/fixtures/sprites/src2"), ]); - let sprites = SpriteSources::resolve(&mut cfg).unwrap().0; + let sprites = SpriteSources::resolve(&mut cfg).unwrap().sources; assert_eq!(sprites.len(), 2); - test_src(sprites.values(), 1, "all_1").await; - test_src(sprites.values(), 2, "all_2").await; + //.sdf => generate sdf from png, add sdf == true + //- => does not generate sdf, omits sdf == true + for extension in ["_sdf", ""] { + test_src(sprites.values(), 1, "all_1", extension).await; + test_src(sprites.values(), 2, "all_2", extension).await; - test_src(sprites.get("src1").into_iter(), 1, "src1_1").await; - test_src(sprites.get("src1").into_iter(), 2, "src1_2").await; + test_src(sprites.get("src1").into_iter(), 1, "src1_1", extension).await; + test_src(sprites.get("src1").into_iter(), 2, "src1_2", extension).await; - test_src(sprites.get("src2").into_iter(), 1, "src2_1").await; - test_src(sprites.get("src2").into_iter(), 2, "src2_2").await; + test_src(sprites.get("src2").into_iter(), 1, "src2_1", extension).await; + test_src(sprites.get("src2").into_iter(), 2, "src2_2", extension).await; + } } async fn test_src( sources: impl Iterator, pixel_ratio: u8, filename: &str, + extension: &str, ) { - let path = PathBuf::from(format!("../tests/fixtures/sprites/expected/{filename}")); - - let sprites = get_spritesheet(sources, pixel_ratio).await.unwrap(); + let path = PathBuf::from(format!( + "../tests/fixtures/sprites/expected/{filename}{extension}" + )); + let sprites = get_spritesheet(sources, pixel_ratio, extension == "_sdf") + .await + .unwrap(); let mut json = serde_json::to_string_pretty(sprites.get_index()).unwrap(); json.push('\n'); let png = sprites.encode_png().unwrap(); diff --git a/tests/config.yaml b/tests/config.yaml index 70b08a05c..d28a0f007 100644 --- a/tests/config.yaml +++ b/tests/config.yaml @@ -179,6 +179,7 @@ sprites: paths: tests/fixtures/sprites/src1 sources: mysrc: tests/fixtures/sprites/src2 + make_sdf: false fonts: - tests/fixtures/fonts/overpass-mono-regular.ttf diff --git a/tests/expected/configured/save_config.yaml b/tests/expected/configured/save_config.yaml index 308490c10..1b91e08fe 100644 --- a/tests/expected/configured/save_config.yaml +++ b/tests/expected/configured/save_config.yaml @@ -170,6 +170,7 @@ sprites: paths: tests/fixtures/sprites/src1 sources: mysrc: tests/fixtures/sprites/src2 + make_sdf: false fonts: - tests/fixtures/fonts/overpass-mono-regular.ttf - tests/fixtures/fonts diff --git a/tests/fixtures/sprites/expected/all_1_sdf.json b/tests/fixtures/sprites/expected/all_1_sdf.json new file mode 100644 index 000000000..1ca1cd595 --- /dev/null +++ b/tests/fixtures/sprites/expected/all_1_sdf.json @@ -0,0 +1,34 @@ +{ + "another_bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 20, + "y": 16, + "sdf": true + }, + "bear": { + "height": 16, + "pixelRatio": 1, + "width": 16, + "x": 20, + "y": 0, + "sdf": true + }, + "bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 35, + "y": 16, + "sdf": true + }, + "sub/circle": { + "height": 20, + "pixelRatio": 1, + "width": 20, + "x": 0, + "y": 0, + "sdf": true + } +} diff --git a/tests/fixtures/sprites/expected/all_1_sdf.png b/tests/fixtures/sprites/expected/all_1_sdf.png new file mode 100644 index 000000000..ffb354692 Binary files /dev/null and b/tests/fixtures/sprites/expected/all_1_sdf.png differ diff --git a/tests/fixtures/sprites/expected/all_2_sdf.json b/tests/fixtures/sprites/expected/all_2_sdf.json new file mode 100644 index 000000000..5d1fb2e53 --- /dev/null +++ b/tests/fixtures/sprites/expected/all_2_sdf.json @@ -0,0 +1,34 @@ +{ + "another_bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 40, + "y": 32, + "sdf": true + }, + "bear": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 40, + "y": 0, + "sdf": true + }, + "bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 70, + "y": 32, + "sdf": true + }, + "sub/circle": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 0, + "y": 0, + "sdf": true + } +} diff --git a/tests/fixtures/sprites/expected/all_2_sdf.png b/tests/fixtures/sprites/expected/all_2_sdf.png new file mode 100644 index 000000000..e1ce6514b Binary files /dev/null and b/tests/fixtures/sprites/expected/all_2_sdf.png differ diff --git a/tests/fixtures/sprites/expected/src1_1_sdf.json b/tests/fixtures/sprites/expected/src1_1_sdf.json new file mode 100644 index 000000000..05675714b --- /dev/null +++ b/tests/fixtures/sprites/expected/src1_1_sdf.json @@ -0,0 +1,26 @@ +{ + "another_bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 20, + "y": 16, + "sdf": true + }, + "bear": { + "height": 16, + "pixelRatio": 1, + "width": 16, + "x": 20, + "y": 0, + "sdf": true + }, + "sub/circle": { + "height": 20, + "pixelRatio": 1, + "width": 20, + "x": 0, + "y": 0, + "sdf": true + } +} diff --git a/tests/fixtures/sprites/expected/src1_1_sdf.png b/tests/fixtures/sprites/expected/src1_1_sdf.png new file mode 100644 index 000000000..b3c301e82 Binary files /dev/null and b/tests/fixtures/sprites/expected/src1_1_sdf.png differ diff --git a/tests/fixtures/sprites/expected/src1_2_sdf.json b/tests/fixtures/sprites/expected/src1_2_sdf.json new file mode 100644 index 000000000..1430954ff --- /dev/null +++ b/tests/fixtures/sprites/expected/src1_2_sdf.json @@ -0,0 +1,26 @@ +{ + "another_bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 40, + "y": 32, + "sdf": true + }, + "bear": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 40, + "y": 0, + "sdf": true + }, + "sub/circle": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 0, + "y": 0, + "sdf": true + } +} diff --git a/tests/fixtures/sprites/expected/src1_2_sdf.png b/tests/fixtures/sprites/expected/src1_2_sdf.png new file mode 100644 index 000000000..c291da958 Binary files /dev/null and b/tests/fixtures/sprites/expected/src1_2_sdf.png differ diff --git a/tests/fixtures/sprites/expected/src2_1_sdf.json b/tests/fixtures/sprites/expected/src2_1_sdf.json new file mode 100644 index 000000000..a7f71e2be --- /dev/null +++ b/tests/fixtures/sprites/expected/src2_1_sdf.json @@ -0,0 +1,10 @@ +{ + "bicycle": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 0, + "y": 0, + "sdf": true + } +} diff --git a/tests/fixtures/sprites/expected/src2_1_sdf.png b/tests/fixtures/sprites/expected/src2_1_sdf.png new file mode 100644 index 000000000..db2640780 Binary files /dev/null and b/tests/fixtures/sprites/expected/src2_1_sdf.png differ diff --git a/tests/fixtures/sprites/expected/src2_2_sdf.json b/tests/fixtures/sprites/expected/src2_2_sdf.json new file mode 100644 index 000000000..8cd9eaa1a --- /dev/null +++ b/tests/fixtures/sprites/expected/src2_2_sdf.json @@ -0,0 +1,10 @@ +{ + "bicycle": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 0, + "y": 0, + "sdf": true + } +} diff --git a/tests/fixtures/sprites/expected/src2_2_sdf.png b/tests/fixtures/sprites/expected/src2_2_sdf.png new file mode 100644 index 000000000..7c947fd36 Binary files /dev/null and b/tests/fixtures/sprites/expected/src2_2_sdf.png differ