|
@@ -1,89 +1,19 @@
|
|
-use std::fs::File;
|
|
|
|
-
|
|
|
|
-use clap::Parser;
|
|
|
|
-use serde::Deserialize;
|
|
|
|
-
|
|
|
|
|
|
+mod data;
|
|
|
|
+mod draw;
|
|
mod image;
|
|
mod image;
|
|
|
|
+mod opts;
|
|
|
|
|
|
-use crate::image::{Image, Pixel};
|
|
|
|
-
|
|
|
|
-#[derive(Deserialize, Debug)]
|
|
|
|
-struct ColorEntry {
|
|
|
|
- color: Pixel,
|
|
|
|
- symbol: String,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-#[derive(Deserialize, Debug)]
|
|
|
|
-struct Mapping(Vec<ColorEntry>);
|
|
|
|
-
|
|
|
|
-impl Mapping {
|
|
|
|
- fn load(path: impl AsRef<std::path::Path>) -> Result<Mapping, Box<dyn std::error::Error>> {
|
|
|
|
- Ok(serde_yaml::from_reader(File::open(path)?)?)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn lookup(&self, color: Pixel) -> &str {
|
|
|
|
- let found = self.0.iter().find(|entry| entry.color == color);
|
|
|
|
- match found {
|
|
|
|
- Some(entry) => &entry.symbol,
|
|
|
|
- None => panic!("Unable to find entry for {:?}", color),
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-#[derive(Deserialize)]
|
|
|
|
-struct Config {
|
|
|
|
- grid_every: u32,
|
|
|
|
- line_weight: f64,
|
|
|
|
- major_line_weight: f64,
|
|
|
|
- grid_size: f64,
|
|
|
|
- font: String,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl Config {
|
|
|
|
- fn scale(&self, n: u32) -> f64 {
|
|
|
|
- n as f64 * self.grid_size
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl Default for Config {
|
|
|
|
- fn default() -> Config {
|
|
|
|
- Config {
|
|
|
|
- grid_every: 10,
|
|
|
|
- line_weight: 1.0,
|
|
|
|
- major_line_weight: 3.0,
|
|
|
|
- grid_size: 24.0,
|
|
|
|
- font: "Fira Sans 12".to_string(),
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-#[derive(Parser, Debug)]
|
|
|
|
-#[clap(author, version, about, long_about = None)]
|
|
|
|
-struct Options {
|
|
|
|
- image: String,
|
|
|
|
- mapping: String,
|
|
|
|
- output: Option<String>,
|
|
|
|
-
|
|
|
|
- #[clap(long, default_value_t = 10)]
|
|
|
|
- grid: u32,
|
|
|
|
-
|
|
|
|
- #[clap(long, default_value_t = 1.0)]
|
|
|
|
- line_weight: f64,
|
|
|
|
-
|
|
|
|
- #[clap(long, default_value_t = 3.0)]
|
|
|
|
- major_line_weight: f64,
|
|
|
|
-
|
|
|
|
- #[clap(long, default_value_t = 24.0)]
|
|
|
|
- size: f64,
|
|
|
|
|
|
+use clap::Parser;
|
|
|
|
|
|
- #[clap(long, default_value_t = String::from("Fira Sans 12"))]
|
|
|
|
- font: String,
|
|
|
|
-}
|
|
|
|
|
|
+use crate::data::{Config, Mapping};
|
|
|
|
+use crate::draw::Pattern;
|
|
|
|
+use crate::image::Image;
|
|
|
|
+use crate::opts::Options;
|
|
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let opts = Options::parse();
|
|
let opts = Options::parse();
|
|
|
|
|
|
- let img = Image::load(opts.image)?;
|
|
|
|
|
|
+ let image = Image::load(opts.image)?;
|
|
let mapping = Mapping::load(opts.mapping)?;
|
|
let mapping = Mapping::load(opts.mapping)?;
|
|
|
|
|
|
let config = Config {
|
|
let config = Config {
|
|
@@ -95,53 +25,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
};
|
|
};
|
|
|
|
|
|
let surf = cairo::SvgSurface::new(
|
|
let surf = cairo::SvgSurface::new(
|
|
- config.scale(img.width),
|
|
|
|
- config.scale(img.height),
|
|
|
|
|
|
+ config.scale(image.width),
|
|
|
|
+ config.scale(image.height),
|
|
Some(opts.output.unwrap_or_else(|| "output.svg".to_string())),
|
|
Some(opts.output.unwrap_or_else(|| "output.svg".to_string())),
|
|
)?;
|
|
)?;
|
|
|
|
|
|
- let ctx = cairo::Context::new(&surf)?;
|
|
|
|
- ctx.set_source_rgb(1.0, 1.0, 1.0);
|
|
|
|
- ctx.paint()?;
|
|
|
|
-
|
|
|
|
- let layout = pangocairo::functions::create_layout(&ctx).unwrap();
|
|
|
|
- let font = pango::FontDescription::from_string(&config.font);
|
|
|
|
- layout.set_width(2880);
|
|
|
|
- layout.set_font_description(Some(&font));
|
|
|
|
-
|
|
|
|
- ctx.set_source_rgb(0.0, 0.0, 0.0);
|
|
|
|
- for i in 0..=img.width {
|
|
|
|
- ctx.move_to(config.scale(i), 0.0);
|
|
|
|
- ctx.line_to(config.scale(i), config.scale(img.height));
|
|
|
|
- ctx.set_line_width(if i % config.grid_every == 0 {
|
|
|
|
- config.major_line_weight
|
|
|
|
- } else {
|
|
|
|
- config.line_weight
|
|
|
|
- });
|
|
|
|
- ctx.stroke()?;
|
|
|
|
- }
|
|
|
|
- for j in 0..=img.height {
|
|
|
|
- ctx.move_to(0.0, config.scale(j));
|
|
|
|
- ctx.line_to(config.scale(img.width), config.scale(j));
|
|
|
|
- ctx.set_line_width(if j % config.grid_every == 0 {
|
|
|
|
- config.major_line_weight
|
|
|
|
- } else {
|
|
|
|
- config.line_weight
|
|
|
|
- });
|
|
|
|
- ctx.stroke()?;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- ctx.set_source_rgb(0.0, 0.0, 0.0);
|
|
|
|
-
|
|
|
|
- for ((x, y), pixel) in img.iter() {
|
|
|
|
- let l = mapping.lookup(pixel);
|
|
|
|
-
|
|
|
|
- if !l.is_empty() {
|
|
|
|
- ctx.move_to(config.scale(x) + 5.0, config.scale(y) + 3.0);
|
|
|
|
- layout.set_text(l);
|
|
|
|
- pangocairo::functions::show_layout(&ctx, &layout);
|
|
|
|
- }
|
|
|
|
|
|
+ Pattern {
|
|
|
|
+ image,
|
|
|
|
+ config,
|
|
|
|
+ mapping,
|
|
}
|
|
}
|
|
|
|
+ .draw(cairo::Context::new(&surf)?)?;
|
|
|
|
|
|
surf.finish();
|
|
surf.finish();
|
|
|
|
|