1#![allow(clippy::borrow_deref_ref)]
3
4pub mod conversions;
5pub mod utils;
6
7use crate::blending::params::{BlendAlgorithmParams, Options};
8use crate::blending::{
9 blend_images, demultiply_image, get_blending_algorithm, is_algorithm_multiplied, BlendAlgorithm,
10};
11use crate::constants;
12use crate::errors::PConvertError;
13use crate::parallelism::{ResultMessage, ThreadPool};
14use crate::utils::{read_png_from_file, write_png_parallel, write_png_to_file};
15use pyo3::exceptions::PyException;
16use pyo3::prelude::*;
17use pyo3::types::{IntoPyDict, PyDict, PySequence};
18use std::sync::mpsc;
19use utils::{
20 build_algorithm, build_params, get_compression_type, get_filter_type, get_num_threads,
21};
22
23static mut THREAD_POOL: Option<ThreadPool> = None;
24
25#[pymodule]
26fn pconvert_rust(_py: Python, module: &PyModule) -> PyResult<()> {
27 unsafe {
28 let mut thread_pool = ThreadPool::new(constants::DEFAULT_THREAD_POOL_SIZE).unwrap();
29 thread_pool.start();
30 THREAD_POOL = Some(thread_pool);
31 }
32
33 module.add("COMPILATION_DATE", constants::COMPILATION_DATE)?;
34 module.add("COMPILATION_TIME", constants::COMPILATION_TIME)?;
35 module.add("VERSION", constants::VERSION)?;
36 module.add("ALGORITHMS", constants::ALGORITHMS.to_vec())?;
37 module.add("COMPILER", constants::COMPILER)?;
38 module.add("COMPILER_VERSION", constants::COMPILER_VERSION)?;
39 module.add("LIBPNG_VERSION", constants::LIBPNG_VERSION)?;
40 module.add("FEATURES", constants::FEATURES.to_vec())?;
41 module.add("PLATFORM_CPU_BITS", constants::PLATFORM_CPU_BITS)?;
42
43 let filters: Vec<String> = constants::FILTER_TYPES
44 .to_vec()
45 .iter()
46 .map(|x| format!("{:?}", x))
47 .collect();
48 module.add("FILTER_TYPES", filters)?;
49
50 let compressions: Vec<String> = constants::COMPRESSION_TYPES
51 .to_vec()
52 .iter()
53 .map(|x| format!("{:?}", x))
54 .collect();
55 module.add("COMPRESSION_TYPES", compressions)?;
56
57 #[pyfunction]
58 #[pyo3(name = "blend_images")]
59 fn blend_images_py(
60 py: Python,
61 bot_path: String,
62 top_path: String,
63 target_path: String,
64 algorithm: Option<String>,
65 is_inline: Option<bool>,
66 options: Option<Options>,
67 ) -> PyResult<()> {
68 py.allow_threads(|| -> PyResult<()> {
71 let num_threads = get_num_threads(&options);
72 if num_threads == 0 {
73 blend_images_single_thread(
74 bot_path,
75 top_path,
76 target_path,
77 algorithm,
78 is_inline,
79 options,
80 )
81 } else {
82 unsafe {
83 blend_images_multi_thread(
84 bot_path,
85 top_path,
86 target_path,
87 algorithm,
88 is_inline,
89 options,
90 num_threads,
91 )
92 }
93 }
94 })
95 }
96
97 #[pyfunction]
98 #[pyo3(name = "blend_multiple")]
99 fn blend_multiple_py(
100 py: Python,
101 img_paths: &PySequence,
102 out_path: String,
103 algorithm: Option<String>,
104 algorithms: Option<&PySequence>,
105 is_inline: Option<bool>,
106 options: Option<Options>,
107 ) -> PyResult<()> {
108 let img_paths: Vec<String> = img_paths.extract()?;
110 let num_images = img_paths.len();
111
112 let algorithms_to_apply: Vec<(BlendAlgorithm, Option<BlendAlgorithmParams>)> =
113 match (algorithms, algorithm) {
114 (Some(algorithms), _) if algorithms.len().unwrap() > 0 => build_params(algorithms)?,
115 (_, Some(algorithm)) => vec![(build_algorithm(&algorithm)?, None); num_images - 1],
116 _ => vec![(BlendAlgorithm::Multiplicative, None); num_images - 1],
117 };
118
119 py.allow_threads(|| -> PyResult<()> {
122 let num_threads = get_num_threads(&options);
123 if num_threads == 0 {
124 blend_multiple_single_thread(
125 img_paths,
126 out_path,
127 algorithms_to_apply,
128 is_inline,
129 options,
130 )
131 } else {
132 unsafe {
133 blend_multiple_multi_thread(
134 img_paths,
135 out_path,
136 algorithms_to_apply,
137 is_inline,
138 options,
139 num_threads,
140 )
141 }
142 }
143 })
144 }
145
146 #[pyfunction]
147 #[pyo3(name = "get_thread_pool_status")]
148 fn get_thread_pool_status(py: Python) -> PyResult<&PyDict> {
149 unsafe {
150 match &mut THREAD_POOL {
151 Some(thread_pool) => {
152 let status_dict = thread_pool.get_status().into_py_dict(py);
153 Ok(status_dict)
154 }
155 None => Err(PyException::new_err(
156 "Accessing global thread pool".to_string(),
157 )),
158 }
159 }
160 }
161
162 module.add_function(wrap_pyfunction!(blend_images_py, module)?)?;
163 module.add_function(wrap_pyfunction!(blend_multiple_py, module)?)?;
164 module.add_function(wrap_pyfunction!(get_thread_pool_status, module)?)?;
165
166 Ok(())
167}
168
169fn blend_images_single_thread(
170 bot_path: String,
171 top_path: String,
172 target_path: String,
173 algorithm: Option<String>,
174 is_inline: Option<bool>,
175 options: Option<Options>,
176) -> PyResult<()> {
177 let algorithm = algorithm.unwrap_or_else(|| String::from("multiplicative"));
178 let algorithm = build_algorithm(&algorithm)?;
179
180 let _is_inline = is_inline.unwrap_or(false);
181
182 let demultiply = is_algorithm_multiplied(&algorithm);
183 let algorithm_fn = get_blending_algorithm(&algorithm);
184
185 let mut bot = read_png_from_file(bot_path, demultiply)?;
186 let top = read_png_from_file(top_path, demultiply)?;
187
188 blend_images(&mut bot, &top, &algorithm_fn, &None);
189
190 let compression_type = get_compression_type(&options);
191 let filter_type = get_filter_type(&options);
192 write_png_to_file(target_path, &bot, compression_type, filter_type)?;
193
194 Ok(())
195}
196
197unsafe fn blend_images_multi_thread(
198 bot_path: String,
199 top_path: String,
200 target_path: String,
201 algorithm: Option<String>,
202 is_inline: Option<bool>,
203 options: Option<Options>,
204 num_threads: usize,
205) -> PyResult<()> {
206 let algorithm = algorithm.unwrap_or_else(|| String::from("multiplicative"));
207 let algorithm = build_algorithm(&algorithm)?;
208 let _is_inline = is_inline.unwrap_or(false);
209 let demultiply = is_algorithm_multiplied(&algorithm);
210 let algorithm_fn = get_blending_algorithm(&algorithm);
211
212 let thread_pool = match &mut THREAD_POOL {
213 Some(thread_pool) => thread_pool,
214 None => panic!("Unable to access global pconvert thread pool"),
215 };
216
217 thread_pool.expand_to(num_threads);
219
220 let bot_result_channel = thread_pool
221 .execute(move || ResultMessage::ImageResult(read_png_from_file(bot_path, demultiply)));
222 let top_result_channel = thread_pool
223 .execute(move || ResultMessage::ImageResult(read_png_from_file(top_path, demultiply)));
224
225 let mut bot = match bot_result_channel.recv().unwrap() {
226 ResultMessage::ImageResult(result) => result,
227 }?;
228 let top = match top_result_channel.recv().unwrap() {
229 ResultMessage::ImageResult(result) => result,
230 }?;
231
232 blend_images(&mut bot, &top, &algorithm_fn, &None);
233
234 let compression_type = get_compression_type(&options);
235 let filter_type = get_filter_type(&options);
236 write_png_parallel(target_path, &bot, compression_type, filter_type)?;
237
238 Ok(())
239}
240
241fn blend_multiple_single_thread(
242 img_paths: Vec<String>,
243 out_path: String,
244 algorithms: Vec<(BlendAlgorithm, Option<BlendAlgorithmParams>)>,
245 is_inline: Option<bool>,
246 options: Option<Options>,
247) -> PyResult<()> {
248 let num_images = img_paths.len();
249
250 if num_images < 1 {
251 return Err(PyErr::from(PConvertError::ArgumentError(
252 "ArgumentError: 'img_paths' must contain at least one path".to_string(),
253 )));
254 }
255
256 if algorithms.len() != num_images - 1 {
257 return Err(PyErr::from(PConvertError::ArgumentError(format!(
258 "ArgumentError: 'algorithms' must be of size {} (one per blending operation)",
259 num_images - 1
260 ))));
261 };
262
263 let _is_inline = is_inline.unwrap_or(false);
264
265 let mut img_paths_iter = img_paths.iter();
268 let first_path = img_paths_iter.next().unwrap().to_string();
269 let first_demultiply = if !algorithms.is_empty() {
270 is_algorithm_multiplied(&algorithms[0].0)
271 } else {
272 false
273 };
274 let mut composition = read_png_from_file(first_path, first_demultiply)?;
275 let zip_iter = img_paths_iter.zip(algorithms.iter());
276 for pair in zip_iter {
277 let path = pair.0.to_string();
278 let (algorithm, algorithm_params) = pair.1;
279 let demultiply = is_algorithm_multiplied(algorithm);
280 let algorithm_fn = get_blending_algorithm(algorithm);
281 let current_layer = read_png_from_file(path, demultiply)?;
282 blend_images(
283 &mut composition,
284 ¤t_layer,
285 &algorithm_fn,
286 algorithm_params,
287 );
288 }
289
290 let compression_type = get_compression_type(&options);
291 let filter_type = get_filter_type(&options);
292 write_png_to_file(out_path, &composition, compression_type, filter_type)?;
293
294 Ok(())
295}
296
297unsafe fn blend_multiple_multi_thread(
298 img_paths: Vec<String>,
299 out_path: String,
300 algorithms: Vec<(BlendAlgorithm, Option<BlendAlgorithmParams>)>,
301 is_inline: Option<bool>,
302 options: Option<Options>,
303 num_threads: usize,
304) -> PyResult<()> {
305 let num_images = img_paths.len();
306
307 if num_images < 1 {
308 return Err(PyErr::from(PConvertError::ArgumentError(
309 "ArgumentError: 'img_paths' must contain at least one path".to_string(),
310 )));
311 }
312
313 if algorithms.len() != num_images - 1 {
314 return Err(PyErr::from(PConvertError::ArgumentError(format!(
315 "ArgumentError: 'algorithms' must be of size {} (one per blending operation)",
316 num_images - 1
317 ))));
318 };
319
320 let _is_inline = is_inline.unwrap_or(false);
321
322 let thread_pool = match &mut THREAD_POOL {
323 Some(thread_pool) => thread_pool,
324 None => panic!("Unable to access global pconvert thread pool"),
325 };
326
327 thread_pool.expand_to(num_threads);
329
330 let mut png_channels: Vec<mpsc::Receiver<ResultMessage>> = Vec::with_capacity(num_images);
331 for path in img_paths.into_iter() {
332 let result_channel = thread_pool.execute(move || -> ResultMessage {
333 ResultMessage::ImageResult(read_png_from_file(path, false))
334 });
335 png_channels.push(result_channel);
336 }
337
338 let first_demultiply = if !algorithms.is_empty() {
339 is_algorithm_multiplied(&algorithms[0].0)
340 } else {
341 false
342 };
343
344 let mut composition = match png_channels[0].recv().unwrap() {
345 ResultMessage::ImageResult(result) => result,
346 }?;
347 if first_demultiply {
348 demultiply_image(&mut composition)
349 }
350
351 for i in 1..png_channels.len() {
355 let (algorithm, algorithm_params) = &algorithms[i - 1];
356 let demultiply = is_algorithm_multiplied(algorithm);
357 let algorithm_fn = get_blending_algorithm(algorithm);
358 let mut current_layer = match png_channels[i].recv().unwrap() {
359 ResultMessage::ImageResult(result) => result,
360 }?;
361 if demultiply {
362 demultiply_image(&mut current_layer)
363 }
364
365 blend_images(
366 &mut composition,
367 ¤t_layer,
368 &algorithm_fn,
369 algorithm_params,
370 );
371 }
372
373 let compression_type = get_compression_type(&options);
374 let filter_type = get_filter_type(&options);
375 write_png_parallel(out_path, &composition, compression_type, filter_type)?;
376
377 Ok(())
378}