123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- use std::fs::File;
- use clap::Parser;
- use serde::Deserialize;
- mod image;
- 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,
- #[clap(long, default_value_t = String::from("Fira Sans 12"))]
- font: String,
- }
- fn main() -> Result<(), Box<dyn std::error::Error>> {
- let opts = Options::parse();
- let img = Image::load(opts.image)?;
- let mapping = Mapping::load(opts.mapping)?;
- let config = Config {
- grid_every: opts.grid,
- line_weight: opts.line_weight,
- major_line_weight: opts.major_line_weight,
- grid_size: opts.size,
- font: opts.font.clone(),
- };
- let surf = cairo::SvgSurface::new(
- config.scale(img.width),
- config.scale(img.height),
- 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);
- }
- }
- surf.finish();
- Ok(())
- }
|