pconvert_rust/
compose.rs

1use crate::benchmark::Benchmark;
2use crate::blending::{
3    blend_images, get_blending_algorithm, is_algorithm_multiplied, multiply_image, BlendAlgorithm,
4};
5use crate::errors::PConvertError;
6use crate::parallelism::{ResultMessage, ThreadPool};
7use crate::utils::{read_png_from_file, write_png_parallel, write_png_to_file};
8use image::codecs::png::{CompressionType, FilterType};
9use image::Rgba;
10use std::fmt::{Display, Formatter};
11use std::{fmt, sync::mpsc::Receiver};
12
13const THREAD_POOL_SIZE: usize = 5;
14
15#[derive(Clone)]
16pub enum Background {
17    Alpha,
18    White,
19    Blue,
20    Texture,
21}
22
23impl Display for Background {
24    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
25        match self {
26            Background::Alpha => write!(f, "alpha"),
27            Background::White => write!(f, "white"),
28            Background::Blue => write!(f, "blue"),
29            Background::Texture => write!(f, "texture"),
30        }
31    }
32}
33
34/// Testing utility that composes an image made up of the specified
35/// background image, using the specified algorithm, compression and filter types.
36/// Looks for the layers and outputs the final composition to the given `dir` and
37/// takes track of times spent in each phase in the benchmark struct
38pub fn compose(
39    dir: &str,
40    algorithm: BlendAlgorithm,
41    background: &Background,
42    compression: CompressionType,
43    filter: FilterType,
44    benchmark: &mut Benchmark,
45) -> Result<String, PConvertError> {
46    let demultiply = is_algorithm_multiplied(&algorithm);
47
48    let algorithm_fn = get_blending_algorithm(&algorithm);
49
50    // reads one PNG at the time and blends it with the current result
51    // these values are hardcoded by the multiple layer files
52    let background_file = format!("background_{}.png", background);
53    let png_file_names = vec![
54        "sole.png",
55        "back.png",
56        "front.png",
57        "shoelace.png",
58        &background_file,
59    ];
60
61    let png_paths = png_file_names
62        .iter()
63        .map(|name| format!("{}{}", dir, name))
64        .collect::<Vec<String>>();
65
66    let top = benchmark.execute(Benchmark::add_read_png_time, || {
67        read_png_from_file(format!("{}sole.png", dir), demultiply)
68    })?;
69
70    let mut bot =
71        png_paths[..png_file_names.len() - 1]
72            .iter()
73            .fold(top, |mut composition, path| {
74                let layer = benchmark
75                    .execute(Benchmark::add_read_png_time, || {
76                        read_png_from_file(path.clone(), demultiply)
77                    })
78                    .unwrap();
79
80                benchmark.execute(Benchmark::add_blend_time, || {
81                    blend_images(&mut composition, &layer, &algorithm_fn, &None)
82                });
83
84                composition
85            });
86
87    if demultiply {
88        benchmark.execute(Benchmark::add_blend_time, || multiply_image(&mut bot));
89    }
90
91    let mut composition = benchmark.execute(Benchmark::add_read_png_time, || {
92        read_png_from_file(format!("{}background_{}.png", dir, background), false)
93    })?;
94
95    benchmark.execute(Benchmark::add_blend_time, || {
96        blend_images(&mut composition, &bot, &algorithm_fn, &None)
97    });
98
99    // writes the final composition to the file system
100    let file_name = format!(
101        "result_{}_{}_{:#?}_{:#?}.png",
102        algorithm, background, compression, filter
103    );
104    let file_out = format!("{}{}", dir, file_name);
105    benchmark.execute(Benchmark::add_write_png_time, || {
106        write_png_to_file(file_out, &composition, compression, filter)
107    })?;
108
109    Ok(file_name)
110}
111
112/// Multi-threaded version of the `compose` testing utility
113/// Reads each PNG in a different thread and makes use of the
114/// `mtpng` library to write the final composition
115pub fn compose_parallel(
116    dir: &str,
117    algorithm: BlendAlgorithm,
118    background: &Background,
119    compression: CompressionType,
120    filter: FilterType,
121    benchmark: &mut Benchmark,
122) -> Result<String, PConvertError> {
123    let demultiply = is_algorithm_multiplied(&algorithm);
124    let algorithm_fn = get_blending_algorithm(&algorithm);
125
126    let mut thread_pool = ThreadPool::new(THREAD_POOL_SIZE)?;
127    thread_pool.start();
128
129    // sends the PNG reading tasks to multiple threads
130    // these values are hardcoded by the multiple layer files
131    let background_file = format!("background_{}.png", background);
132    let png_file_names = vec![
133        "sole.png",
134        "back.png",
135        "front.png",
136        "shoelace.png",
137        &background_file,
138    ];
139
140    let result_channels = png_file_names
141        .iter()
142        .map(|name| format!("{}{}", dir, name))
143        .map(|path| {
144            thread_pool
145                .execute(move || ResultMessage::ImageResult(read_png_from_file(path, demultiply)))
146        })
147        .collect::<Vec<Receiver<ResultMessage>>>();
148
149    // blending phase, will run the multiple layers operation
150    // as expected by the proper execution
151    let mut bot = benchmark.execute(Benchmark::add_read_png_time, || {
152        if let Ok(ResultMessage::ImageResult(result)) = result_channels[0].recv() {
153            result
154        } else {
155            panic!("failure reading 'sole.png'")
156        }
157    })?;
158
159    for i in 1..=3 {
160        let top = benchmark.execute(Benchmark::add_read_png_time, || {
161            if let Ok(ResultMessage::ImageResult(result)) = result_channels[i].recv() {
162                result
163            } else {
164                panic!("failure reading '{}'", png_file_names[i])
165            }
166        })?;
167        benchmark.execute(Benchmark::add_blend_time, || {
168            blend_images(&mut bot, &top, &algorithm_fn, &None)
169        });
170    }
171
172    if demultiply {
173        multiply_image(&mut bot);
174    }
175
176    let mut composition = benchmark.execute(Benchmark::add_read_png_time, || {
177        if let Ok(ResultMessage::ImageResult(result)) = result_channels[4].recv() {
178            result
179        } else {
180            panic!("failure reading '{}'", background_file)
181        }
182    })?;
183    benchmark.execute(Benchmark::add_blend_time, || {
184        blend_images(&mut composition, &bot, &algorithm_fn, &None)
185    });
186
187    // writes the final composition PNG to the output file,
188    // this is considered to be the most expensive operation
189    let file_name = format!(
190        "result_{}_{}_{:#?}_{:#?}.png",
191        algorithm, background, compression, filter
192    );
193    let file_out = format!("{}{}", dir, file_name);
194    benchmark.execute(Benchmark::add_write_png_time, || {
195        write_png_parallel(file_out, &composition, compression, filter)
196    })?;
197
198    Ok(file_name)
199}
200
201/// Testing utility that applies a blue-ish filter to an image
202pub fn apply_blue_filter(pixel: &mut Rgba<u8>) {
203    // sets red value to 0 and green value to the blue one (blue filter effect)
204    pixel[0] = 0;
205    pixel[1] = pixel[2];
206}