rusty_ppm/
ppm_reader.rs

1// Copyright (c) 2024 Remi A. Godin
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20use std::path::Path;
21use std::fs::{read, read_to_string};
22use std::slice::Iter;
23
24use cgmath::{Vector3,vec3};
25use simple_canvas::Canvas;
26
27use crate::utils::complete_path;
28
29#[derive(Debug)]
30pub enum PpmReaderError{
31    ImageHeaderCouldNotBeRead,
32    ImageDoesNotExistAtPath
33}
34
35impl std::error::Error for PpmReaderError{}
36impl std::fmt::Display for PpmReaderError{
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        write!(f, "There was an error when trying to read an image file.")
39    }
40}
41
42/// This function reads from a .ppm file at the specified path and outputs a `Canvas<Vector3<u8>>` object containing its data.
43///
44/// Example usage:
45/// ```Rust
46/// let new_canvas: Canvas<Vector3<u8>> = read_to_canvas(Path::new("./my_image.ppm"));
47/// ```
48pub fn read_ppm(directory: &Path, file_name: &str) -> Result<Canvas<Vector3<u8>>, Box<dyn std::error::Error>> {
49    let full_path = complete_path(directory, file_name);
50
51    if full_path.try_exists()? {
52        let file = read(&full_path)?;
53        let mut file_iter: Iter<'_, u8> = file.iter();
54        let mut header: String = "".to_string();
55        header.push(*file_iter.next().unwrap() as char);
56        header.push(*file_iter.next().unwrap() as char);
57        file_iter.next(); // skip newline
58        if header.eq("P6") {
59            return Ok(read_binary_image(&mut file_iter));
60        }
61        else if header.eq("P3") {
62            return Ok(read_string_image(&full_path));
63        } else {
64            Err(Box::new(PpmReaderError::ImageHeaderCouldNotBeRead))
65        }
66    } else {
67        Err(Box::new(PpmReaderError::ImageDoesNotExistAtPath))
68    }
69}
70
71/// This function parses a string ppm file and creates a `Canvas<Vector3<u8>>` from it
72fn read_string_image(path: &Path) -> Canvas<Vector3<u8>> {
73    let file = read_to_string(path).unwrap();
74    let mut file_iter = file.split([' ', '\n']);
75    file_iter.next(); //skip header
76    let width: usize = file_iter.next().unwrap().parse::<usize>().unwrap();
77    let height: usize = file_iter.next().unwrap().parse::<usize>().unwrap();
78    let _color_code = file_iter.next(); // skip color code
79    let size = file_iter.size_hint().0;
80    if size != width * height && size % 3 != 0 {
81        println!("Actual size: {} * {} = {}", width, height, width * height);
82        println!("Iter size: {}", size);
83        panic!("Some pixel data seems to be missing or the file might be corrupted.")
84    };
85    let mut canvas: Canvas<Vector3<u8>> = Canvas::new(width, height, vec3(0,0,0));
86    for pixel in canvas.iter_mut() {
87        *pixel = vec3(file_iter.next().unwrap().parse::<u8>().unwrap(),
88            file_iter.next().unwrap().parse::<u8>().unwrap(),
89            file_iter.next().unwrap().parse::<u8>().unwrap()
90        )
91    }
92    canvas
93
94}
95
96/// This function parses a binary ppm file and creates a `Canvas<Vector3<u8>>` from it
97fn read_binary_image(file_iter: &mut Iter<'_, u8>) -> Canvas<Vector3<u8>> {
98    let width_iter = file_iter.cloned().take_while(|e| (*e <= 57 && *e >= 48));
99    let mut width_str: String = "".to_string();
100    width_iter.for_each(|e| width_str.push(e as char));
101
102    let height_iter = file_iter.cloned().take_while(|e| (*e <= 57 && *e >= 48));
103    let mut height_str: String = "".to_string();
104    height_iter.for_each(|e| height_str.push(e as char));
105
106    let color_code_iter = file_iter.cloned().take_while(|e| (*e <= 57 && *e >= 48));
107    let mut color_code_str: String = "".to_string();
108    color_code_iter.for_each(|e| color_code_str.push(e as char));
109
110    let width = width_str.parse::<usize>().expect("Could not parse");
111    let height = height_str.parse::<usize>().expect("Could not parse");
112
113    let size = file_iter.size_hint().0;
114    if size != width * height && size % 3 != 0 {
115        println!("Actual size: {} * {} = {}", width, height, width * height);
116        println!("Iter size: {}", size);
117        panic!("Size issue")
118    }
119    let mut canvas: Canvas<Vector3<u8>> = Canvas::new(width, height, vec3(0,0,0));
120    for pixel in canvas.iter_mut() {
121        *pixel = vec3(*file_iter.next().unwrap(), *file_iter.next().unwrap(), *file_iter.next().unwrap());
122    }
123    canvas
124}