Skip to content
Snippets Groups Projects
pipeline.rs 5.19 KiB
Newer Older
Louis's avatar
Louis committed
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,
Louis's avatar
Louis committed
		actions: Vec<CrunchCommand>,
Louis's avatar
Louis committed
	},
	Ref {
		input_path: String,
		output_path: String,
		reference: String,
	},
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PipelineRef {
Louis's avatar
Louis committed
	pub actions: Vec<CrunchCommand>,
Louis's avatar
Louis committed
}

#[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())?;
Louis's avatar
Louis committed
	let pipeline_data: PipelineFile = if path.ends_with(".toml") {
Louis's avatar
Louis committed
		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
Louis's avatar
Louis committed
				.get(reference.as_str())
Louis's avatar
Louis committed
				.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();

Louis's avatar
Louis committed
			if let Err(e) = std::fs::create_dir(&outer_target_path) {
				match e.kind() {
Louis's avatar
Louis committed
					std::io::ErrorKind::AlreadyExists => { /* This is fine */ }
					_ => log::error!(
						"Failed to create containing directory {}; {}",
						outer_target_path.to_string_lossy(),
						e
					),
Louis's avatar
Louis committed
				}
Louis's avatar
Louis committed
			}

			match file.save(output_path) {
				Ok(_) => {}
				Err(e) => {
					log::error!("Failed to save to {}; {}", output_path, e);
				}
			}
		});

	Ok(())
}