Skip to content

Commit

Permalink
implemented sdf
Browse files Browse the repository at this point in the history
  • Loading branch information
CommanderStorm committed Aug 30, 2024
1 parent 7212e12 commit 4342a85
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 18 deletions.
11 changes: 11 additions & 0 deletions docs/src/sources-sprites.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,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, `--sprite` can be replaces with `--sdf-sprite`.
This makes images be handled as a SDFs allowing their color to be set at runtime in the map redering enines.

```bash
martin --sprite /path/to/sprite_a --sprite /path/to/other/sprite_b
```

### Configuring with Config File

A sprite directory can be configured from the config file with the `sprite` key, similar to
Expand All @@ -68,4 +75,8 @@ sprites:
sources:
# SVG images in this directory will be published as a "my_sprites" sprite source
my_sprites: /path/to/some_dir
# This tells Martin to handle images in directories as Signed Distance Fields (SDFs)
# Images are handled as a signed-distance field (SDF) allow their color to be set at runtime in the map redering enines.
# Defaults to `false`.
make_sdf: false
```
10 changes: 9 additions & 1 deletion martin/src/args/root.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::BTreeSet;
use std::path::PathBuf;

use clap::Parser;
Expand All @@ -9,6 +10,7 @@ use crate::args::srv::SrvArgs;
use crate::config::Config;
#[cfg(any(feature = "mbtiles", feature = "pmtiles", feature = "sprites"))]
use crate::file_config::FileConfigEnum;
use crate::sprites::SpriteConfig;
use crate::MartinError::ConfigAndConnectionsError;
use crate::{MartinResult, OptOneMany};

Expand Down Expand Up @@ -59,6 +61,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<PathBuf>,
/// tells Martin to handle images in directories as Signed Distance Fields (SDFs)
/// Images handled as a SDF allow their color to be set at runtime in the map redering enines.
///
/// 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<PathBuf>,
Expand Down Expand Up @@ -109,7 +117,7 @@ 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,BTreeMap::new(), SpriteConfig{make_sdf:self.extras.make_sdf, ..Default::default()});
}

if !self.extras.font.is_empty() {
Expand Down
61 changes: 44 additions & 17 deletions martin/src/sprites/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ pub type SpriteCatalog = BTreeMap<String, CatalogSpriteEntry>;

#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct SpriteConfig {
/// This tells Martin to handle images in directories as Signed Distance Fields (SDFs)
/// Images are handled as a signed-distance field (SDF) allow their color to be set at runtime in the map redering enines.
///
/// Defaults to `false`.
pub make_sdf: bool,
#[serde(flatten)]
pub unrecognized: UnrecognizedValues,
}
Expand All @@ -71,15 +76,25 @@ impl ConfigExtras for SpriteConfig {
}

#[derive(Debug, Clone, Default)]
pub struct SpriteSources(HashMap<String, SpriteSource>);
pub struct SpriteSources {
sources: HashMap<String, SpriteSource>,
/// This tells Martin to handle images in directories as Signed Distance Fields (SDFs)
/// Images are handled as a signed-distance field (SDF) allow their color to be set at runtime in the map redering enines.
///
/// Defaults to `false`.
make_sdf: bool,
}

impl SpriteSources {
pub fn resolve(config: &mut FileConfigEnum<SpriteConfig>) -> FileResult<Self> {
let Some(cfg) = config.extract_file_config(None)? else {
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();

Expand Down Expand Up @@ -110,7 +125,7 @@ impl SpriteSources {
pub fn get_catalog(&self) -> SpriteResult<SpriteCatalog> {
// 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());
Expand All @@ -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());
Expand All @@ -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::<SpriteResult<Vec<_>>>()?;

get_spritesheet(sprite_ids.into_iter(), dpi).await
get_spritesheet(sprite_ids.into_iter(), dpi, self.make_sdf).await
}
}

Expand Down Expand Up @@ -194,6 +209,7 @@ async fn parse_sprite(
pub async fn get_spritesheet(
sources: impl Iterator<Item = &SpriteSource>,
pixel_ratio: u8,
make_sdf: bool,
) -> SpriteResult<Spritesheet> {
// Asynchronously load all SVG files from the given sources
let mut futures = Vec::new();
Expand All @@ -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
Expand All @@ -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<Item = &SpriteSource>,
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();
Expand All @@ -269,7 +296,7 @@ mod tests {
#[cfg(not(feature = "bless-tests"))]
{
let expected = std::fs::read_to_string(path.with_extension("json"))
.expect("Unable to open expected JSON file, make sure to bless tests with\n cargo test --features bless-tests\n");
.expect("Unable to open expected JSON file, make sure to bless tests with\n cargo test --features bless-tests,sprites\n");

assert_eq!(
serde_json::from_str::<serde_json::Value>(&json).unwrap(),
Expand Down

0 comments on commit 4342a85

Please sign in to comment.