|  | @@ -1,6 +1,7 @@
 | 
	
		
			
				|  |  |  use serde::Deserialize;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -use crate::image::Pixel;
 | 
	
		
			
				|  |  | +use crate::image::{Image, Pixel};
 | 
	
		
			
				|  |  | +use std::collections::{HashMap, HashSet};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #[derive(Deserialize, Debug)]
 | 
	
		
			
				|  |  |  struct ColorEntry {
 | 
	
	
		
			
				|  | @@ -9,19 +10,61 @@ struct ColorEntry {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #[derive(Deserialize, Debug)]
 | 
	
		
			
				|  |  | -pub struct Mapping(Vec<ColorEntry>);
 | 
	
		
			
				|  |  | +pub struct Mapping(HashMap<Pixel, String>);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#[derive(Debug)]
 | 
	
		
			
				|  |  | +pub struct IncompleteMappingError {
 | 
	
		
			
				|  |  | +    path: std::path::PathBuf,
 | 
	
		
			
				|  |  | +    missing_colors: Vec<Pixel>,
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl std::fmt::Display for IncompleteMappingError {
 | 
	
		
			
				|  |  | +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
	
		
			
				|  |  | +        writeln!(
 | 
	
		
			
				|  |  | +            f,
 | 
	
		
			
				|  |  | +            "`{:?}` is missing entries for the following pixel colors:",
 | 
	
		
			
				|  |  | +            self.path
 | 
	
		
			
				|  |  | +        )?;
 | 
	
		
			
				|  |  | +        for color in self.missing_colors.iter() {
 | 
	
		
			
				|  |  | +            writeln!(f, "  - {:?}", color)?;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        Ok(())
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl std::error::Error for IncompleteMappingError {}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  impl Mapping {
 | 
	
		
			
				|  |  | -    pub fn load(path: impl AsRef<std::path::Path>) -> Result<Mapping, Box<dyn std::error::Error>> {
 | 
	
		
			
				|  |  | -        Ok(serde_yaml::from_reader(std::fs::File::open(path)?)?)
 | 
	
		
			
				|  |  | +    pub fn load(
 | 
	
		
			
				|  |  | +        path: impl AsRef<std::path::Path>,
 | 
	
		
			
				|  |  | +        img: &Image,
 | 
	
		
			
				|  |  | +    ) -> Result<Mapping, Box<dyn std::error::Error>> {
 | 
	
		
			
				|  |  | +        let path = path.as_ref().to_path_buf();
 | 
	
		
			
				|  |  | +        let data: Vec<ColorEntry> = serde_yaml::from_reader(std::fs::File::open(&path)?)?;
 | 
	
		
			
				|  |  | +        // do validation to make sure all pixel colors are handled
 | 
	
		
			
				|  |  | +        let color_map = data
 | 
	
		
			
				|  |  | +            .into_iter()
 | 
	
		
			
				|  |  | +            .map(|entry| (entry.color, entry.symbol))
 | 
	
		
			
				|  |  | +            .collect::<HashMap<Pixel, String>>();
 | 
	
		
			
				|  |  | +        let all_image_colors = img
 | 
	
		
			
				|  |  | +            .iter()
 | 
	
		
			
				|  |  | +            .map(|(_, color)| color)
 | 
	
		
			
				|  |  | +            .collect::<HashSet<Pixel>>();
 | 
	
		
			
				|  |  | +        let missing_colors = all_image_colors
 | 
	
		
			
				|  |  | +            .into_iter()
 | 
	
		
			
				|  |  | +            .filter(|c| !color_map.contains_key(&c))
 | 
	
		
			
				|  |  | +            .collect::<Vec<Pixel>>();
 | 
	
		
			
				|  |  | +        if !missing_colors.is_empty() {
 | 
	
		
			
				|  |  | +            return Err(Box::new(IncompleteMappingError {
 | 
	
		
			
				|  |  | +                path,
 | 
	
		
			
				|  |  | +                missing_colors,
 | 
	
		
			
				|  |  | +            }));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        Ok(Mapping(color_map))
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      pub 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),
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +        &self.0[&color]
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 |