pconvert_rust/pymodule/
utils.rs

1//! Utility functions for argument parsing from python input to inner-crate rust types.
2
3use crate::blending::params::{BlendAlgorithmParams, Options, Value};
4use crate::blending::BlendAlgorithm;
5use crate::errors::PConvertError;
6use crate::utils::{image_compression_from, image_filter_from};
7use image::codecs::png::{CompressionType, FilterType};
8use pyo3::prelude::*;
9use pyo3::types::{PySequence, PyString};
10use std::str::FromStr;
11
12/// Attempts to parse a `&String` to a `BlendAlgorithm`.
13/// Returns the enum variant if it succeeds. Otherwise it returns a `PyErr`.
14pub fn build_algorithm(algorithm: &str) -> Result<BlendAlgorithm, PyErr> {
15    match BlendAlgorithm::from_str(algorithm) {
16        Ok(algorithm) => Ok(algorithm),
17        Err(algorithm) => Err(PyErr::from(PConvertError::ArgumentError(format!(
18            "ArgumentError: invalid algorithm '{}'",
19            algorithm
20        )))),
21    }
22}
23
24/// Attempts to build a vector of blending operations and extra parameters.
25/// One pair per blending operation. Returns a `PyErr` if it fails parsing.
26pub fn build_params(
27    algorithms: &PySequence,
28) -> Result<Vec<(BlendAlgorithm, Option<BlendAlgorithmParams>)>, PyErr> {
29    let mut result = Vec::new();
30
31    // parses the parameter sequence which is a python sequence (tuple or list)
32    // made of either algorithms or more sequences of algorithms and special parameters
33    for i in 0..algorithms.len()? {
34        let element = algorithms.get_item(i)?;
35
36        if let Ok(string) = element.cast_as::<PyString>() {
37            let algorithm = build_algorithm(&string.to_string())?;
38            result.push((algorithm, None));
39        } else if let Ok(sequence) = element.cast_as::<PySequence>() {
40            let algorithm = sequence.get_item(0)?.extract::<String>()?;
41            let algorithm = build_algorithm(&algorithm)?;
42
43            let mut blending_params = BlendAlgorithmParams::new();
44            let params_sequence = sequence.get_item(1)?;
45            if let Ok(params_sequence) = params_sequence.cast_as::<PySequence>() {
46                for j in 0..params_sequence.len()? {
47                    if let Ok(property_value) = params_sequence.get_item(j)?.cast_as::<PySequence>()
48                    {
49                        let param_name = property_value.get_item(0)?.extract::<String>()?;
50                        let param_value = property_value.get_item(1)?;
51                        let param_value = param_value.extract::<Value>()?;
52                        blending_params.insert(param_name, param_value);
53                    }
54                }
55            } else {
56                return Err(PyErr::from(PConvertError::ArgumentError(
57                    "Parameters should be given as a python sequence object".to_string(),
58                )));
59            }
60
61            result.push((algorithm, Some(blending_params)));
62        }
63    }
64
65    Ok(result)
66}
67
68/// Retrieves the `image::codecs::png::CompressionType` value from the `Options` map if it exists.
69/// Otherwise it returns the default value: `CompressionType::Fast`.
70pub fn get_compression_type(options: &Option<Options>) -> CompressionType {
71    options.clone().map_or(CompressionType::Fast, |options| {
72        options
73            .get("compression")
74            .map_or(CompressionType::Fast, |compression| match compression {
75                Value::Str(compression) => image_compression_from(compression.to_string()),
76                _ => CompressionType::Fast,
77            })
78    })
79}
80
81/// Retrieves the `image::codecs::png::FilterType` value from the `Options` map if it exists.
82/// Otherwise it returns the default value: `FilterType::NoFilter`.
83pub fn get_filter_type(options: &Option<Options>) -> FilterType {
84    options.clone().map_or(FilterType::NoFilter, |options| {
85        options
86            .get("filter")
87            .map_or(FilterType::NoFilter, |filter| match filter {
88                Value::Str(filter) => image_filter_from(filter.to_string()),
89                _ => FilterType::NoFilter,
90            })
91    })
92}
93
94/// Retrieves the number of threads value from the `Options` map if it exists.
95/// Otherwise it returns the default value: 0.
96pub fn get_num_threads(options: &Option<Options>) -> usize {
97    options.clone().map_or(0, |options| {
98        options
99            .get("num_threads")
100            .map_or(0, |num_threads| match num_threads {
101                Value::UInt(num_threads) => *num_threads,
102                _ => 0,
103            })
104    })
105}