pixel_weaver/
render.rs

1use cgmath::{vec2, Vector2, Vector3};
2use crate::ImageData;
3use simple_canvas::*;
4use std::sync::{Arc, Mutex};
5use std::thread;
6use std::collections::VecDeque;
7
8/// Single threaded pixel-by-pixel image writer.
9///
10/// ## Parameters
11/// - `canvas`:     The Canvas struct to write the image to. 
12/// - `pixel_func`: The function that will run on each pixel of the image.
13pub fn main_image(
14    canvas: &mut Canvas<Vector3<u8>>,
15    pixel_func: impl Fn(&ImageData, &Vector2<u32>) -> Vector3<u8> + Send + 'static + Copy
16) {
17    let image_data = ImageData {
18            resolution: vec2(canvas.width, canvas.height),
19            aspect_ratio: canvas.width as f64 / canvas.height as f64,
20            size: (canvas.width * canvas.height)
21        };
22    for row in 0..canvas.height {
23        for col in 0..canvas.width {
24            *canvas.get_mut(row, col).expect("Index out of bounds") =
25                pixel_func(&image_data, &vec2(row as u32, col as u32));
26        }
27    }
28}
29
30/// Multi threaded pixel-by-pixel image writer.
31///
32/// ## Parameters
33/// - `canvas`:       The Canvas struct to write the image to. 
34/// - `pixel_func`:   The function that will run on each pixel of the image.
35/// - `thread_count`: The number of threads to use to render the image
36pub fn main_image_mt(
37    canvas: &mut Canvas<Vector3<u8>>,
38    pixel_func: impl Fn(&ImageData, &Vector2<u32>) -> Vector3<u8> + Send + 'static + Copy,
39    thread_count: usize
40) {
41    // Ensure that the thread count is not bigger than the pixel count (very unlikely)
42    if thread_count > (canvas.width * canvas.height) {
43        panic!("Cannot have more thread than pixels");
44    }
45
46    // Create thread readable image data 
47    let image_data = Arc::new(
48        ImageData {
49            resolution: vec2(canvas.width, canvas.height),
50            aspect_ratio: canvas.width as f64 / canvas.height as f64,
51            size: (canvas.width * canvas.height)
52        }
53    );
54    
55    // Calculate the slice size. Slices are pieces of the images that will be used by the different
56    // threads.
57    let slice_size = image_data.size / thread_count;
58
59    // The diff is the the difference between the offset times the thread count, and the actual
60    // size of the image. This is to adjust for rounding errors when dividing.
61    let slice_diff = image_data.size - (slice_size * thread_count);
62
63    // Init the vector that will hold the slice of the image
64    let mut slices: VecDeque<Vec<Vector3<u8>>> = VecDeque::new();
65    
66    // Create the data slices
67    for i in 0..thread_count {
68        let mut curr_size = slice_size;
69        if i == thread_count - 1 {
70            curr_size += slice_diff;
71        }
72        let mut slice: Vec<Vector3<u8>> = Vec::with_capacity(curr_size as usize);
73        for j in (i * slice_size as usize)..(i * slice_size as usize + curr_size as usize) {
74            slice.push(*canvas.data.get(j).unwrap());
75        }
76        slices.push_front(slice);
77    }
78
79    
80    // Init the threads vector
81    let mut threads: Vec<thread::JoinHandle<_>> = Vec::new();
82
83    // Create the result vector
84    let mut result_vec: Vec<Vec<Vector3<u8>>> = Vec::with_capacity(thread_count);
85
86    // Temp vector to act as placeholder for our individual slices
87    let placeholder_vec: Vec<Vector3<u8>> = Vec::new();
88
89    // For each thread, push the temp vector
90    (0..thread_count).for_each(|_| result_vec.push(placeholder_vec.clone()));
91
92    // Wrap our result vector in a Mutex
93    let result_vec = Arc::new(Mutex::new(result_vec));
94    
95    // Init the Arc variables to pass the offset to our threads
96    let offset = Arc::new(slice_size as usize);
97
98    // Create and run the threads
99    for i in 0..thread_count {
100        
101        // Clone the Arc variables for use inside the threads
102        let image_data = Arc::clone(&image_data);
103        //let image_size = Arc::clone(&image_size);
104        //let image_width = Arc::clone(&image_width);
105        //let image_height = Arc::clone(&image_height);
106        let offset = Arc::clone(&offset);
107        let mut slice = slices.pop_back().unwrap();
108        let result_vec = Arc::clone(&result_vec);
109
110        //Create the threads and run the function on each pixel
111        threads.push(thread::spawn(move || {
112
113            // Create a variable to store the current offset of the thread. This is only to be able
114            // to add the diff we calculated when slicing our original image. I can't change the
115            // offset directly since we still need it for other calculations.
116            let mut curr_offset = *offset;
117
118            // If the last thread is running, add the diff to the current offset
119            if i == thread_count - 1 {
120                curr_offset += slice_diff;
121            }
122
123            // Iterate over the pixel of the current slice and run the pixel function on each of
124            // them.
125            let mut curr_row;
126            let mut curr_col;
127            for index in ((i * *offset)..(i * *offset + curr_offset)).enumerate() {
128                // Figure out the row and col of the current image from its linear representation.
129                curr_row = (index.1) / image_data.resolution.x;
130                curr_col = (index.1) - curr_row * image_data.resolution.x;
131
132                // Run the pixel function
133                let pixel = pixel_func(&image_data, &vec2(curr_row as u32, curr_col as u32));
134                *slice.get_mut(index.0).unwrap() = pixel;
135            }
136
137            // Lock the result vector Mutex to the current thread to allow it to add its slice to
138            // it.
139            let mut result_vec = result_vec.lock().unwrap();
140            *result_vec.get_mut(i).unwrap() = slice;
141        }));
142    };
143
144    // Join the threads
145    for thread in threads {
146        thread.join().expect("Could not join thread.");
147    }
148
149    // Create a new data vector the same as the one from the Canvas
150    let mut data_vec: Vec<Vector3<u8>> = Vec::with_capacity(image_data.size);
151
152    // Add all the slices from the result vector together into the previously create data vector
153    for slice in result_vec.lock().unwrap().iter() {
154        for pixel in slice {
155            data_vec.push(*pixel);
156        }
157    }
158
159    // Set the canvas data to the data calculated in the multithreaded function.
160    canvas.data = data_vec;
161}