use std::collections::HashMap; use std::path::PathBuf; use rayon::prelude::*; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::cli_args::CrunchCommand; use crate::{commands, load_image}; #[derive(Error, Debug)] pub enum PipelineError { #[error("Use a file ending with '.toml' or '.json' to configure your pipeline")] FormatDetection, } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum PipelineType { Pipeline { input_path: String, output_path: String, actions: Vec<CrunchCommand>, }, Ref { input_path: String, output_path: String, reference: String, }, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PipelineRef { pub actions: Vec<CrunchCommand>, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PipelineFile { pub refs: HashMap<String, PipelineRef>, pub pipelines: Vec<PipelineType>, } pub fn execute_pipeline<IN: ToString, OUT: ToString>( file_path: IN, _outpath: OUT, ) -> anyhow::Result<()> { let path = file_path.to_string(); if !&path.ends_with(".toml") && !&path.ends_with(".json") { Err(PipelineError::FormatDetection)?; } let file_contents = std::fs::read(file_path.to_string())?; let pipeline_data: PipelineFile = if path.ends_with(".toml") { toml::from_slice(&file_contents)? } else { serde_json::from_slice(&file_contents)? }; pipeline_data .pipelines .par_iter() .filter_map(|pipe| match pipe { PipelineType::Pipeline { input_path, output_path, actions, } => Some((input_path, output_path, actions.clone())), PipelineType::Ref { input_path, output_path, reference, } => pipeline_data .refs .get(reference.as_str()) .map(|value| (input_path, output_path, value.actions.clone())), }) .for_each(|(input_path, output_path, actions)| { let mut file = match load_image(input_path, None) { Ok(image) => image, Err(e) => { log::error!("Error loading {}; {:?}", input_path, e); return; } }; let mut count = 1; for step in actions { match step { CrunchCommand::Extrude { tile_size, space_y, space_x, pad_y, pad_x, extrude, } => { file = match commands::extrude( file, tile_size, pad_x, pad_y, space_x, space_y, extrude, ) { Ok(f) => f, Err(e) => { log::error!( "Failed to extrude {} at step {}; {}", input_path, count, e ); return; } }; } CrunchCommand::Remap { palette_file } => { let palette_data = match load_image(&palette_file, None) { Ok(p) => p, Err(e) => { log::error!( "Failed to load {} at step {}; {:?}", input_path, count, e ); return; } }; let image_palette = match commands::palette(&file) { Ok(ip) => ip, Err(e) => { log::error!( "Failed to extract palette from {} at step {}; {}", input_path, count, e ); return; } }; let target_palette = match commands::palette(&palette_data) { Ok(tp) => tp, Err(e) => { log::error!( "Failed to extract palette from {} at step {}; {}", &palette_file, count, e ); return; } }; let mappings = commands::calculate_mapping(&image_palette, &target_palette); file = match commands::remap_image(file, mappings) { Ok(f) => f, Err(e) => { log::error!( "Failed to remap {} at step {}; {}", input_path, count, e ); return; } }; } CrunchCommand::Scale { factor } => { file = match commands::rescale(&file, factor) { Ok(f) => f, Err(e) => { log::error!( "Failed to scale {} at step {}; {}", input_path, count, e ); return; } }; } CrunchCommand::Rotate { amount } => { file = match commands::rotate(&file, amount) { Ok(f) => f, Err(e) => { log::error!( "Failed to rotate {} by {:?} step(s); {}", input_path, amount, e ); return; } }; } CrunchCommand::Flip { direction } => { file = match commands::flip(&file, direction) { Ok(f) => f, Err(e) => { log::error!( "Failed to flip {} in the following direction: {:?}; {}", input_path, direction, e ); return; } }; } CrunchCommand::Palette { .. } | CrunchCommand::Pipeline => continue, } count += 1; } let mut outer_target_path = PathBuf::from(output_path); outer_target_path.pop(); if let Err(e) = std::fs::create_dir(&outer_target_path) { match e.kind() { std::io::ErrorKind::AlreadyExists => { /* This is fine */ } _ => log::error!( "Failed to create containing directory {}; {}", outer_target_path.to_string_lossy(), e ), } } match file.save(output_path) { Ok(_) => {} Err(e) => { log::error!("Failed to save to {}; {}", output_path, e); } } }); Ok(()) }